trsing’s diary

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

EFFECTIVE C# 6.0/7.0 読書メモ 項目21

項目21 破棄可能な型引数をサポートするようにジェネリック型を作成すること

型引数がIDisposableを実装している場合にも対応するようにジェネリック型を設計すること。

例1:メソッド内で型引数のインスタンスを作成して使用するようなジェネリックメソッドがある場合

   public void GetThingsDone()
    {
        T driver = new T();
        driver.DoWork();
    }

TがIDisposableを実装する場合、リソースリークが起こる。

例1の解決策

TがIDisposableを実装しているか確認し、実装している場合は適切に破棄する。 usingステートメントに入れるとよい。

   public void GetThingsDone()
    {
        T driver = new T();
        using (driver as IDisposable)
        {
            driver.DoWork();
        }
    }

TがIDisposableを実装していればDispose()が呼び出される。実装していない場合呼び出されない。

例2:型引数のインスタンスをメンバ変数として使用する場合

IDisposableを実装しているかもしれない型への参照を保持することになる。

例2の解決策

ジェネリック型においてもIDisposableを実装する。型引数にIDisposableが実装されているかどうかを確認し、実装されている場合には破棄するように実装する。

注意点
  • sealedクラスとしないなら、派生クラスにおいてもDispose()メソッドが呼び出せるようにIDisposableパターンを実装する
  • 複数回Dispose()を呼べるようになっていること

その他解決策

Dispose()を呼び出す責任をジェネリッククラスの外に任せ、オブジェクトの所有権をジェネリッククラスの外に委ねる。

   private T driver;
    public EngineDriver(T driver)
    {
        this.driver = driver;
    }

例1の解決策のIL

詳しくは本文のusingステートメントの動作に関する説明参照

    // Methods
    .method public hidebysig 
        instance void GetThingsDone () cil managed 
    {
        // Method begins at RVA 0x205c
        // Code size 44 (0x2c)
        .maxstack 1
        .locals init (
            [0] !T,
            [1] class [mscorlib]System.IDisposable
        )

        IL_0000: call !!0 [mscorlib]System.Activator::CreateInstance<!T>()
        IL_0005: stloc.0
        IL_0006: ldloc.0
        IL_0007: box !T
        IL_000c: isinst [mscorlib]System.IDisposable//オブジェクト参照が特定のクラスか。結果はスタックにプッシュされる
        IL_0011: stloc.1//スタックからをローカル変数1に値(isinstの結果)を取り出す
        .try
        {
            IL_0012: ldloca.s 0
            IL_0014: constrained. !T
            IL_001a: callvirt instance void C/IEngine::DoWork()
            IL_001f: leave.s IL_002b
        } // end .try
        finally
        {
            // sequence point: hidden
            IL_0021: ldloc.1//スタックにローカル変数1の値(isinstの結果)を置く
            IL_0022: brfalse.s IL_002a//スタックの値が0ならIL_002aに分岐

            IL_0024: ldloc.1
            IL_0025: callvirt instance void [mscorlib]System.IDisposable::Dispose()

            // sequence point: hidden
            IL_002a: endfinally
        } // end handler

        IL_002b: ret
    } // end of method EngineDriverOne`1::GetThingsDone

次サイトでIL確認

sharplab.io

命令文の意味とか

docs.microsoft.com

www5b.biglobe.ne.jp