LoginSignup
24
49

More than 1 year has passed since last update.

【C#個人勉強用】Effective C# 6.0/7.0 第2章:リソース管理

Last updated at Posted at 2022-02-06

完全に個人の勉強用メモです。

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3339393631302f64636631336438622d383233392d663837312d346466312d3365663066373130653937382e706e67.png

第1章はこちら

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

  • イベントハンドラやデリゲートの使用で自身が意図するよりもオブジェクトがメモリ上に存在し続けることがあり得る
  • 非マネージリソースの存在期間を開発者が制御できるようにするために、ファイナライザとIDisposableインターフェイスという2つのメカニズムが用意されている
  • ファイナライザはオブジェクトがガベージとなったあと、不特定のタイミングでガベージコレクタによって呼び出される
  • しかし、ファイナライザは特定のタイミングで実行されるわけではないので、なるべくファイナライザに頼らないような設計あるいはコーディングを行うべき

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

  • クラスのコンストラクタとメンバ変数の整合性が取れなくなるということがよく起こる
  • この問題を回避するには、各コンストラクタ中でメンバ変数を初期化するのではなく、「メンバ変数を宣言した時点で初期化する」
  • staticおよび非staticメンバ変数のいずれに対しても初期化子(initializer)の構文を使用すべき
  • C#ではメンバ変数の宣言と同時に初期化することがある
public class MyClass
{
        // コレクションの宣言と同時に初期化
        private List<string> labels = new List<string>();
}
  • コンストラクタがいくつ追加されても、Labelsメンバ変数は適切に初期化される
  • 初期化コードは変数を定義している場所にあるだけで十分
  • オブジェクト初期化子を使用することによって、型のメンバ変数が未初期化状態になることを防げる
  • 初期化に対するコードは各コンストラクタよりも先に実行される
  • 初期化子を使用することにより、将来新しいコンストラクタを追加した場合でもメンバ変数の初期化忘れを防ぐことができる

オブジェクト初期化子を使用してはいけないケース

① オブジェクトを0またはnullに初期化するケース
② 同一オブジェクトに対して複数回の初期化を行う

Listオブジェクトがそれぞれ別の値になる
public class MyClass
{
        // コレクションを宣言して初期化
        private List<string> labels = new List<string>();

        MyClass()
        {
        }

        MyClass(int size)
        {
                labels = new List<string>(size);
        }
}

③ 初期化時における例外処理に対応させる場合
- メンバ変数の初期化中に発生し得る全ての例外は、mオブジェクトの外側へ伝搬されるため、クラス内部で復旧用のコードを用意することができない

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

  • staticメンバ変数は、型のインスタンスが1つ以上作成されるよりも前に初期化されるべき
  • C#ではstaticコンストラクタという構文が用意されている
  • C#でシングルトンパターンを実装する場合、staticコンストラクタを使用する方法が最も一般的
    • インスタンス生成用のコンストラクタはprivateにしておき、インスタンス取得用のメンバを用意する
public class MySingleton
{
        private static readonly MySingleton theOneAndOnly = new MySingleton();

        public static MySwngleton TheOnly
        {
                get { return theOneAndOnly; }
        }

        private MySingleton()
        {
        }
}
  • staticコンストラクタはCLRから呼び出されるため、コンストラクタ中で起こり得る例外には細心の注意が必要
  • staticメンバに対してオブジェクト初期化子を使用する場合、発生した例外をクラス地震で処理することはできない
  • 一方、staticコンストラクタの場合にはクラス内で例外処理が可能
static MySingleton()
{
        try
        {
                theOneAndOnly = new MySingleton();
        }
        catch
        {
                // この位置で復旧処理を実行
        }
}
  • static変数初期化子およびstaticコンストラクタは、クラス内のstaticメンバを初期化する場合に最適な方法

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

  • C#コンパイラはコンストラクタ初期化子を特別な文法とみなし、重複した変数初期化子を削除したり、重複した親クラスのコンストラクタ呼び出しを削除したりする
  • 結果として実行時において、オブジェクトの初期化に必要なコード量が最小限に抑えられる

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

  • ガベージコレクタにできるだけ仕事をさせないようにすべき
  • ローカル変数が参照型であり、なおかつ非常に頻繁に呼び出されるメソッド中で使用する必要がある場合には、その変数おwメンバ変数へ昇格すべき(値型のおrーかる変数については特に必要なし)
悪い例
protected override void OnPaint(PaintEventArgs e)
{
        // 悪い例。行がイベントが起こるたびに同じフォントが生成される
        using (Font MyFont = new Font("Arial", 10.0f))
        {
                e.Graphics.DrawString(DateTime.Now.ToString(),
                        MyFont, Brushes.Black, new PointF(0, 0);
        }
        base.OnPaint(e);
}
  • Fontオブジェクトをローカル変数からメンバ変数に昇格させる
メンバ変数へ昇格
private readonly Font myFont = new Font("Arial", 10.0f);

protected override void OnPaint(PaintEventArgs e)
{
        e.Graphics.DrawString(DateTime.Now.ToString(),
        myFont, Brushes.Black, new PointF(0, 0);
        base.OnPaint(e);
}
  • 参照型のインスタンスが頻繁に使用される場合、一般的にはstaticメンバ変数を方に用意すると良い
private static Brush blackBrush;
public static Brush Black
{
        get
        {
                if (blackBrush == null)
                        blackBrush = new SolidBrush(Color.Black);
                return blackBrush;
        }
}

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

  • オブジェクトは全てのコンストラクタが実行し終わるまでは完全には作成されないので
  • オブジェクトの初期化中に仮想メソッドを呼び出すと思いがけない動作をすることがある
  • コンストラクタないで仮想メソッドを呼ぶようなコードは極めて不安定
  • 派生クラスでは全てのインスタンス変数を初期化子で初期化しなければいけない
  • これらの制御は非常に困難

項目17:標準的なDisposeパターンを実装する

  • メモリ以外のリソースを保持する型に置いて、メモリ管理コードをどのように作成すべきか
  • クラス階層のルートとなる親クラスでは、以下の規則に従うべき
    • リソースを解放するために、IDisposableインターフェイスを実装すること
    • クラスが非マネージリソースを直接扱う場合に限り、防衛策としてファイナライざを追加すること
    • Disposeとファイナライ座はいずれも、派生クラスにおいてリソース管理を独自にオーバーライドできるよう、仮想メソッドに処理を宇多寝るようにすること
  • 派生クラスは以下が必要
    • 派生クラスでは、独自のリソースを解放する必要がある場合に限って仮想メソッドをオーバーライドすること
    • クラスのメンバが非マネージリソースである場合に限り、ファイナライザを実装すること
    • 親クラスの仮想メソッドを必ず呼ぶこと
  • 非マネージリソースが確実に解放されるようにするための唯一の方法は、ファイナライザを用意する以外にない
  • リソースを即座に解放する必要がある場合、一般的にはオブジェクトにIDisposableインターフェイスを実装することになる
  • IDisposable.Dispose()メソッド中で行うべき処理は次の4つ
    1. 全ての非マネージリソースの解放
    2. 全てのマネージリソースの解放(イベントの解除も含む)
    3. オブジェクトが吐き済みであることを示すフラグの設定。もしオブジェクトの破棄が完了した後に何らかのあpublicメンバが呼び出された場合、このフラグを確認してObjectDisposedExceptionをスローすること
    4. ファイナライゼーションが行われないよう、GC.SuppressFinalize(this)を呼び出すこと
  • 派生クラスにおけるリソースの解放
    • 派生クラスに置いてファイナライ座やIDisposableをオーバーライドする場合、それらのメソッド中では必ず親メソッドを呼ぶようにする
    • そうしなければ親クラスの保持するリソースを適切に解放できない
24
49
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
49