trsing’s diary

勉強、読んだ本、仕事で調べたこととかのメモ。

#項目15 不必要なオブジェクトの生成を避けること

ガベージコレクタによるメモリ管理にはコストがかかる。 GCの実行条件はメモリの確保量と確保の頻度で決まる。メモリを確保すればするほどより頻繁にGCが実行され、非効率的。 参照型のオブジェクトを大量に使用すれば、アプリケーションのパフォーマンスに大きな影響を与える。

したがって、ヒープベースのオブジェクトの確保・破棄を最小限にして、ガベージコレクタにできるだけ仕事をさせないようにするべき。

ガベージコレクタが行う仕事を最小限に抑えるテクニック

その1 

頻繁に生成・破棄する参照型のローカル変数があればその変数をメンバ変数へ昇格させる。

悪い例

Windowsの描画イベントハンドラにおいてGDIオブジェクトを生成するコード。OnPaintは非常に頻繁に呼び出される。ここで同じ設定のオブジェクトMyFontを毎回生成している。

protected override void OnPaint(PaintEventArgs e )
{
    using(Font MyFont = new Font("Arial", 10f));//毎回同じ設定でFontオブジェクト作成
    {略}
}

MyFontをメンバ変数に昇格させるとよい。毎回の描画のたびにガベージが作られないようになり、ガベージコレクタの仕事量が減る。

private readonly Font MyFont = new Font("Arial", 10f));
protected override void OnPaint(PaintEventArgs e )
{
    略
}
注意点

IDisposableインターフェイスを実装するオブジェクトをローカル変数からメンバ変数へ昇格させる場合、クラス自身にもIDisposableインターフェイスを実装すること(実装方法は項目17を参照)

その2

プログラム中、様々な場所から使用される参照型のインスタンスは、staticメンバ変数にする。

Brushオブジェクト。 プログラム中で多くのウィンドウやコントロールが生成されると、Brushオブジェクトも大量に生成される。そのためBrushオブジェクトを型のメンバ変数へ昇格しただけでは不十分。

.Net FrameworkではBrushesクラスにBrushオブジェクトのstaticメンバを用意している。要求された色のブラシについてのみインスタンスを生成・保持する遅延評価アルゴリズムを採用している。再度その色のブラシが必要になった場合には、保持しているブラシを再利用する。

praivate static Brush blackBrush;
public static Brush Black
{
    get
    {
        if(blackBrush == null)//要求されたときに生成
            blackBrush = new SolidBrush(Color.Black);
        return blackBrush;//以後は生成したブラシを再利用
    }
}

欠点

オブジェクトが不必要に長期間メモリ上に存在することがある。 Disopose()メソッドがいつ呼ばれるのか把握できず、非マネージリソースを破棄できないため。

その3

不変型に対して、それをサポートする可変なビルダクラスを用意する。

悪い例

System.Stringクラスは不変型で、文字列を作成した後はその文字列オブジェクトを変更できない。文字列の内容を変更するようなコードを書いたとしても、実際には新しい文字列オブジェクトが生成され、古いオブジェクトはガベージになる。

string msg = "Hello,";
msg += thisUser.Name;//新しいオブジェクト生成。"Hello,"はガベージに。
msg += ".Today is";//新しいオブジェクト生成。"Hello,<user>"はガベージに
msg += System.DateTime.Now.ToString();//新しいオブジェクト生成。"Hello,<user>.Today is"はガベージに

単純な場合であれば補間文字列を使用、より複雑な文字列操作が必要な倍はStringBuilderクラスを使用する。

StringBuilderは可変な文字列オブジェクトで、不変な文字列オブジェクトを生成できる。StringBuilderで文字列データを生成してから不変な文字列オブジェクトを生成すればよい。

不変型が必要な場合、最終的な不変型のオブジェクトを生成するよりも前に、さまざまな方法で型の情報を変更することが可能なビルダオブジェクトを用意するとよい。