繁体   English   中英

如何在派生类构造函数生成错误时配置C#基类

[英]how to dispose C# base class when derived class constructor generates an error

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

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

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

如果通过异常退出,则所有派生类构造函数必须对正在构造的对象调用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 ,则必须将其处理,除非创建它的代码具有关于将处理推迟到终结器的后果的特定知识。

对于托管资源,这不应该是一个问题,他们将收集垃圾。 对于非托管资源,请确保为对象定义了Finalizer,以确保清除非托管资源。

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


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

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

此解决方案基于@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);
        }
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM