Effective C# 第2章

www.amazon.co.jp 個人的な読書ノートであり、自分の解釈が誤っていたり要点がずれてる可能性があります。内容が気になる人は自分で読んでください。

第2章 リソース管理

GCやメモリ管理や初期化の話。

11. .NETのリソース管理を理解する

  • GCは参照関係を把握していて、どこからも参照されなくなったオブジェクトを削除し、メモリコンパクション(再配置)を行う。
  • アンマネージドなリソースを開放するにはファイナライザとIDisposableを使う。
  • ファイナライザは特定のタイミングで実行されるわけではない点に注意する。
  • GCはファイナライザを呼び出す準備が整ったオブジェクトをキューしておき、次回GCでファイナライザ実行、その次回にオブジェクトを削除する。
  • ファイナライザを持つクラスは、GCの世代(generation)の実装の影響もあるので、予想以上に寿命が伸びることがある。
  • IDisposebleの実装を利用することでこのパフォーマンス低下を避けることができる。

12. メンバには割り当て演算子よりもオブジェクト初期化子を使用すること

  • すべてのコンストラクタに共通した初期化を行う場合は初期化子するのがよい。
  • コンストラクタがいくつ追加されても漏れなく初期化されるから安全。
  • コンストラクタを書かなかった場合にはデフォルトコンストラクタが自動生成され初期化コードが埋め込まれる。
  • 初期化子の初期化は、 親クラスのコンストラクタより先 でかつ 記述された順序 で呼び出される。
  • オブジェクト初期化子を使用すべきでないケース
    • 0 や null で初期化する場合。書かなくていい。むしろパフォーマンスおちる可能性あり。
    • 複数回初期化される可能性がある場合。コンストラクタで初期化する場合は初期化子で作成したオブジェクトはゴミになる。
    • 例外処理に対応させる場合。初期化子は try ~ catch できないので、復旧するコードをクラス内に書けない。

13. static メンバを適切に初期化すること

  • staticコンストラクタはstaticメンバを初期化する最適な方法である。
  • privateメソッドなどは他の方法はよくない。
  • 初期化が重い場合は、Lazyなど遅延評価を使うことを検討する。
  • staticコンストラクタは例外が出るとプログラム停止することに注意する。

14. 初期化ロジックの重複を最小化する

  • コンストラクタをコピペするな。privateメソッドで共通化するな。コンストラクタ初期化子を使え。
public MyClass(int initialCount) : this (initialCount, string.Empty) {}
  • デフォルト引数を使うことでコンストラクタの重複を最小化できる(C#4.0以降)。
  • コンストラクタのオーバーロードよりデフォルト引数を選択するべき。デフォルト値は例外にならないようにするべき。
  • new制約を満たすため、引数なしのコンストラクタを明示的に作成しておくこと。
  • デフォルト引数の名前とデフォルト値は publicインターフェース であるので、利用するコードと強い結合を生み出す点に注意。

インスタンス初回生成時の処理一覧

  1. static 変数メモリストレージの0初期化
  2. static 変数の初期化子実行
  3. 親クラスの static コンストラクタ実行
  4. static コンストラクタ実行
  5. インスタンス変数メモリ・ストレージの0初期化
  6. インスタンス変数の初期化子実行
  7. 親クラスのコンストラクタ実行
  8. インスタンスコンストラクタ実行

2回目以降は 5からスタートする。

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

  • ローカル変数として参照型をたくさんnewするとアロケーションコストが発生してパフォーマンス低下する。
  • 気になるケースではメンバ変数に昇格するとよい。
  • メンバ変数がIDisposableインターフェースを実装している場合、クラス自身もIDisposableを実装すべき。
  • メンバ変数より大域で共有したい場合はstatic変数を利用できる。DIによって外から提供するのも良いだろう。
  • stringの連結は遅いのでStringBuilderを使おう。不変型を生成するときはビルダークラスを提供しよう。

16. コンストラクタ内では仮想メソッドを呼ばないこと

  • 仮想(virtual)メソッドは実行時の型で呼ばれるメソッドが決定される。
  • 小クラスの初期化子実行、親クラスのコンストラクタ実行、小クラスのコンストラクタ実行 の順で初期化される。
  • インスタンスの初期化が不十分な状態で小クラスのメソッドが呼ばれることになる。

17. 標準的なDisposeパターンを実装する

  • 標準的なDisposeパターンを実装することが、非マネージドリソースを解放する正しい方法。正しく理解せよ。
  • Disposeの呼び出しを忘れた場合に備えてファイナライザを準備しておく。
  • 親クラスでは以下の規則に従うべき

    • リソースを解放するために、IDisposableインターフェイスを実装すること。
    • クラスが非マネージリソースを直接扱う場合に限り、防御策としてファイナライザを追加すること。
    • Disposeとファイナライザはいずれも、派生クラスにおいてリソース管理を独自にオーバーライドできるよう、仮想メソッドに処理を委ねるようにすること。
  • 派生クラスでは以下が必要

    • 派生クラスでは、独自のリソースを解放する必要がある場合に限って仮想メソッドをオーバーライドすること。
    • クラスのメンバが非マネージリソースである場合に限り、ファイナライザを実装すること。
    • 親クラスの仮想メソッドを必ず呼ぶこと。
  • 実装例 docs.microsoft.com