简体   繁体   English

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

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

If a C# derived IDisposable class constructor generates an error, how to dispose of the already fully constructed IDisposable base class? 如果C#派生的IDisposable类构造函数生成错误,如何处置已经完全构造的IDisposable基类?

Since all fields in the class hierarchy are initialized before any constructor executes, is it safe for the derived constructor to call base.Dispose()? 由于类层次结构中的所有字段都是在任何构造函数执行之前初始化的,因此派生构造函数调用base.Dispose()是否安全? It violates the rule of not calling virtual methods until object is fully constructed, but I can't think of another way to do it, and my searching hasn't turned up anything about this scenario. 它违反了在完全构造对象之前不调用虚方法的规则,但我想不出另一种方法,并且我的搜索没有发现任何关于这种情况。

My view is that constructors should be light, not relying on external resources/etc that might throw an exception. 我的观点是构造函数应该是轻量级的,而不是依赖于可能引发异常的外部资源/等。 The constructor should do enough to verify Dispose() can be safely called. 构造函数应该做足以验证可以安全地调用Dispose()。 Consider using containment instead of inheritance, or having a factory method do the work that might throw. 考虑使用包含而不是继承,或者使用工厂方法执行可能抛出的工作。

All derived class constructors must call Dispose on the object being constructed if they exit via an exception. 如果通过异常退出,则所有派生类构造函数必须对正在构造的对象调用Dispose Further, it's very difficult to write leak-proof classes if field initializers construct IDisposable instances or can fail. 此外,如果字段初始化程序构造IDisposable实例或可能失败,则编写防漏类非常困难。 Too bad, since requiring objects to be declared in one place, initialized in a second, and cleaned up in a third is not exactly a recipe for coherent code. 太糟糕了,因为要求在一个地方声明对象,在一秒钟内初始化,在第三个地方清理,这并不是连贯代码的配方。

I would suggest that the best pattern is probably something like: 我建议最好的模式可能是这样的:

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();
        }
    }
}

Note that C++/CLI automatically implements that pattern, and can also automatically handle cleanup of IDisposable fields. 请注意,C ++ / CLI会自动实现该模式,并且还可以自动处理IDisposable字段的清理。 Too bad the language seems like a pain in other ways. 太糟糕了,语言在其他方面似乎是一种痛苦。

PS--With relatively few exceptions, mostly revolving around predictable-cost shared immutable objects (eg brushes, fonts, small bitmaps, etc.), code which relies upon Finalize to clean up objects is broken . PS - 除了相对较少的例外,主要围绕可预测成本共享的不可变对象(例如画笔,字体,小位图等),依赖于Finalize来清理对象的代码被打破 If an IDisposable is created, it must be disposed unless the code which created it has particular knowledge about the consequences of deferring disposal to a finalizer. 如果创建了IDisposable ,则必须将其处理,除非创建它的代码具有关于将处理推迟到终结器的后果的特定知识。

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. 对于非托管资源,请确保为对象定义了Finalizer,以确保清除非托管资源。

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. 此外,从构造函数中抛出异常被认为是非常糟糕的方式,最好提供一个工厂方法来进行构造和错误处理,或者为对象配备一个抛出实际异常的Initialize方法。 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. Correct,Dispose不是由垃圾收集器调用的,而是一个Finalizer,它又需要调用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. 您可以调用GC来强制收集运行,您可以等待所有挂起的终结器。 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. 最好不要将异常生成代码放在construstor中,或者在构造函数中放置try / catch来确保在出现错误时调用这些文件。 You can always rethrow the exception later. 您可以随时重新抛出异常。

This solution builds on the proposal by @supercat. 此解决方案基于@supercat的提议。 Initialization of any member that might throw must be performed in the constructor's try/catch block. 必须在构造函数的try / catch块中执行可能抛出的任何成员的初始化。 With that condition met an exception thrown from any constructor will correctly dispose a fully or partially constructed base or derived class. 遇到这种情况,任何构造函数抛出的异常都会正确地处理完全或部分构造的基类或派生类。

In this test code uncomment each of the four exceptions in turn and the program will output which of the Disposable resources were not disposed properly due to the constructor throwing an exception. 在此测试代码中依次取消注释四个异常中的每一个,并且由于构造函数抛出异常,程序将输出哪些Disposable资源未正确处理。 Then uncomment the two Dispose calls and observe that everything gets cleaned up as it should. 然后取消注释两个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