5

如果 C# 派生的 IDisposable 类构造函数产生错误,如何处置已经完全构造的 IDisposable 基类?

由于类层次结构中的所有字段在任何构造函数执行之前都已初始化,派生构造函数调用 base.Dispose() 是否安全?它违反了在对象完全构造之前不调用虚拟方法的规则,但我想不出另一种方法来做到这一点,而且我的搜索没有发现任何关于这种情况的信息。

4

4 回答 4

5

我的观点是构造函数应该是轻量级的,而不是依赖可能引发异常的外部资源/等。构造函数应该做足够的工作来验证 Dispose() 可以安全地调用。考虑使用包含而不是继承,或者让工厂方法完成可能引发的工作。

于 2013-01-17T01:20:28.447 回答
2

如果通过异常退出,所有派生类构造函数都必须调用Dispose正在构造的对象。IDisposable此外,如果字段初始值设定项构造实例或可能失败,则很难编写防泄漏类。太糟糕了,因为要求对象在一个地方声明,在第二个地方初始化,在第三个地方清理并不完全是连贯代码的秘诀。

我建议最好的模式可能是这样的:

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 字段的清理。太糟糕了,这种语言在其他方面似乎很痛苦。

PS——除了相对较少的例外,主要是围绕可预测成本的共享不可变对象(例如画笔、字体、小位图等),依赖于Finalize清理对象的代码被破坏了。如果IDisposable创建了 an ,则必须将其处置,除非创建它的代码对将处置推迟到终结器的后果有特定的了解。

于 2013-01-18T00:09:04.090 回答
0

对于托管资源,这应该不是问题,它们会被垃圾收集。对于非托管资源,请确保为对象定义了终结器,这将确保清理非托管资源。

此外,从构造函数中抛出异常被认为是非常不礼貌的行为,最好提供一个工厂方法来进行构造和错误处理,或者为您的对象配备一个将抛出实际异常的 Initialize 方法。这样,建设总是成功的,你就不会遇到这些类型的问题。


正确,垃圾收集器不调用 Dispose,但 Finalizer 是,它又需要调用 Dispose。这是一种更昂贵的技术,除非使用得当。我的回答中没有这样说,是吗;)。

您可以调用 GC 来强制收集运行,并且可以等待所有待处理的终结器。最好不要将生成异常的代码放在构造函数中,或者在构造函数中的代码周围放置一个 try/catch,以确保在发生错误时对这些文件调用 Dispose。您可以随时重新抛出异常。

于 2013-01-17T01:21:03.460 回答
0

该解决方案基于@supercat 的提议。任何可能抛出的成员的初始化必须在构造函数的 try/catch 块中执行。如果满足该条件,则任何构造函数抛出的异常都将正确处理完全或部分构造的基类或派生类。

在这个测试代码中,依次取消注释四个异常,程序将输出哪些 Disposable 资源由于构造函数抛出异常而未正确释放。然后取消注释两个 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 回答