5

C# から派生した IDisposable クラス コンストラクターがエラーを生成した場合、既に完全に構​​築された IDisposable 基底クラスを破棄する方法は?

クラス階層のすべてのフィールドは、コンストラクターが実行される前に初期化されるため、派生コンストラクターが base.Dispose() を呼び出しても安全ですか? オブジェクトが完全に構築されるまで仮想メソッドを呼び出さないという規則に違反していますが、それを行う別の方法は考えられず、検索してもこのシナリオについて何も見つかりませんでした。

4

4 に答える 4

5

私の見解では、コンストラクターは、例外をスローする可能性のある外部リソースなどに依存するのではなく、軽量である必要があります。コンストラクターは、Dispose()を安全に呼び出すことができることを確認するのに十分なことを行う必要があります。継承の代わりに包含を使用するか、ファクトリメソッドにスローされる可能性のある作業を実行させることを検討してください。

于 2013-01-17T01:20:28.447 に答える
2

すべての派生クラス コンストラクターはDispose、例外によって終了する場合、構築中のオブジェクトを呼び出す必要があります。IDisposableさらに、フィールド初期化子がインスタンスを構築するか失敗する可能性がある場合、漏れ防止クラスを作成することは非常に困難です。残念ながら、オブジェクトを 1 か所で宣言し、2 番目に初期化し、3 番目にクリーンアップする必要があることは、一貫したコードのレシピとは言えません。

最良のパターンはおそらく次のようなものであることをお勧めします。

class foo : baseFoo , IDisposable
{
    foo () : baseFoo
    {
        bool ok = false;
        try
        {
            do_stuff();
            ok = true; // Only after last thing that can cause failure
        }
        finally
        {
            if (!ok)
              Dispose();
        }
    }
}

C++/CLI はそのパターンを自動的に実装し、IDisposable フィールドのクリーンアップも自動的に処理できることに注意してください。残念なことに、言語は別の意味で苦痛のように思えます。

FinalizePS - 比較的少数の例外を除いて、主に予測可能なコストの共有不変オブジェクト (ブラシ、フォント、小さなビットマップなど) を中心に展開し、オブジェクトのクリーンアップに依存するコードは壊れています。が作成された場合IDisposable、それを作成したコードがファイナライザーへの破棄を延期することの結果について特定の知識を持っていない限り、それを破棄する必要があります。

于 2013-01-18T00:09:04.090 に答える
0

このソリューションは、@supercat による提案に基づいています。スローされる可能性のあるメンバーの初期化は、コンストラクターの try/catch ブロックで実行する必要があります。その条件が満たされると、コンストラクターからスローされた例外は、完全または部分的に構築された基本クラスまたは派生クラスを正しく破棄します。

このテスト コードでは、4 つの例外のそれぞれのコメントを順番に解除します。プログラムは、コンストラクターが例外をスローしたために適切に破棄されなかった Disposable リソースを出力します。次に、2 つの Dispose 呼び出しのコメントを外し、すべてが正常にクリーンアップされることを確認します。

    class DisposableResource : IDisposable
    {
        public DisposableResource(string id) { Id = id; }
        ~DisposableResource() { Console.WriteLine(Id + " wasn't disposed.\n"); }
        public string Id { get; private set; }
        public void Dispose() { GC.SuppressFinalize(this); }
    }

    class Base : IDisposable
    {
        public Base()
        {
            try
            {
                throw new Exception();      // Exception 1.
                _baseCtorInit = new DisposableResource("_baseCtorInit");
//              throw new Exception();      // Exception 2.
            }
            catch(Exception)
            {
//              Dispose();                  // Uncomment to perform cleanup.
                throw;
            }
        }

        public virtual void Dispose()
        {
            if (_baseFieldInit != null)
            {
                _baseFieldInit.Dispose();
                _baseFieldInit = null;
            }

            if (_baseCtorInit != null)
            {
                _baseCtorInit.Dispose();
                _baseCtorInit = null;
            }
        }

        private DisposableResource _baseFieldInit = new DisposableResource("_baseFieldInit");
        private DisposableResource _baseCtorInit;
    }

    class Derived : Base
    {
        public Derived()
        {
            try
            {
//              throw new Exception();      // Exception 3.
                _derivedCtorInit = new DisposableResource("_derivedCtorInit");
//              throw new Exception();
            }
            catch (Exception)
            {
//              Dispose();                  // Uncomment to perform cleanup.
                throw;
            }
        }

        public override void Dispose()
        {
            if (_derivedFieldInit != null)
            {
                _derivedFieldInit.Dispose();
                _derivedFieldInit = null;
            }

            if (_derivedCtorInit != null)
            {
                _derivedCtorInit.Dispose();
                _derivedCtorInit = null;
            }

            base.Dispose();
        }

        private DisposableResource _derivedFieldInit = new DisposableResource("_derivedFieldInit");
        private DisposableResource _derivedCtorInit;
    }

    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Derived d = new Derived();
            }
            catch (Exception)
            {
                Console.WriteLine("Caught Exception.\n");
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            Console.WriteLine("\n\nPress any key to continue...\n");
            Console.ReadKey(false);
        }
    }
于 2013-08-21T12:00:57.177 に答える
0

For Managed resources, this should not be a problem, they will get Garbage Collected. For Unmanaged resources, make sure you have a Finalizer defined for your object, that will ensure the Unmanaged resources are cleaned up.

Also, throwing exceptions from a constructor is considered very bad manners, It's better to provide a Factory Method to do the construction and error handling or to equip your object with an Initialize method that will throw the actual exception. That way the construction always succeeds and you're not left with these type of issues.


Correct, Dispose isn't called by the Garbage Collector, but a Finalizer is, which in turn needs to call Dispose. It's a more expensive technique, except when used correctly. I didn't say so in my answer, did I ;).

You can call into the GC to force a collection run and you can wait for all pending finalizers. It's still better to not put the exception generating code in the construstor or by placing a try/catch around the code in the constructor to ensure Dispose is called on these files in case of an error. You can always rethrow the exception later.

于 2013-01-17T01:21:03.460 に答える