代码之家  ›  专栏  ›  技术社区  ›  Sergey Teplyakov

Disposable对象中的构造函数异常

c#
  •  7
  • Sergey Teplyakov  · 技术社区  · 15 年前

    考虑流动类:

    class C1 : IDisposable {...}
    
    class C2 : IDisposable {...}
    
    sealed class C3 : IDisposable
    {
       public C3()
       {
          c1 = new C1();
          throw new Exception(); //oops!
       }
       ~C3()
       {
          //What can we do???
       }
       public void Dispose()
       {
          if ( c1 != null ) c1.Dispose();
          if ( c2 != null ) c2.Dispose();
       }
    
       private C1 c1;
       private C2 c2;
       //assume that this class does not contains native resources
    }
    

    现在,假设我们正确使用一次性物品:

    using (var c3 = new C3())
    {
    }
    

    这个代码片段怎么样?

    我的解决方案非常简单:

    sealed class C3 : IDisposable
    {
       public C3()
       {
          try {
          c1 = new C1();
          throw new Exception(); //oops!
          c2 = new C2();
          }
          catch(Exception)
          {
            DisposeImpl();
            throw;
          }
       }
       ~C3()
       {
          //Not deterministically dispose detected!
          //write to log! 
          //Invalid class usage. Or not??
       }
       public void Dispose()
       {
          DisposeImpl();
       }
       private void DisposeImpl()
       {
         if ( c1 != null ) c1.Dispose();
         if ( c2 != null ) c2.Dispose();
         GC.SuppressFinalize(this); //all resources are released
       }
    
       private C1 c1;
       private C2 c2;
    }
    

    有其他的IDIA、建议或更好的解决方案吗?

    P.S.赫伯·萨特在他的博客中写道( http://herbsutter.wordpress.com/2008/07/25/constructor-exceptions-in-c-c-and-java/

    5 回复  |  直到 5 年前
        1
  •  9
  •   Martijn    5 年前

    你的建议也是我得出的结论 when thinking about this issue

    总之,尽可能多的工作使对象在构造函数中处于可用状态,但如果无法成功完成,请清理在异常处理程序中分配的任何昂贵的托管资源。

    NET中惯用的做法是使用构造函数使对象处于可使用状态。有些人会建议使用一个简单的构造函数,然后使用 Initialize 方法,其中完成了任何“实际”工作以使对象处于正确的状态,但我想不出有哪一个框架类能够做到这一点,因此对于.NET开发人员来说,这不是一种直观的模式,因此不应该在.NET中完成,无论它在其他语言和平台中是否是一种合理的约定。

    无论如何,我认为这是一种相当罕见的情况——通常包装一次性资源的类会将其作为构造函数参数,而不是自己创建。

        2
  •  0
  •   Scoregraphic    15 年前

    更好的解决办法是不这样做 任何 构造函数中的逻辑。只要创建对象,就可以开始了。如果确实需要在构造函数中执行某些操作,请使用try-catch-finally语句对其进行封装,以便释放非托管资源。

        3
  •  0
  •   David Stocking    15 年前

    你的处置实际上不是你的解构主义者吗?

    你基本上是想用RAII吗?

    我的整个计划

    using System;
    
    namespace Testing
    {
        class C1 : IDisposable
        {
            public C1()
            {
            }
            public void Dispose()
            {
                Console.WriteLine( "C1 Destroyed" );
            }
        }
        class C2 : IDisposable
        {
            public C2()
            {
                throw new Exception();
            }
            public void Dispose()
            {
                Console.WriteLine( "C2 Destroyed" );
            }
        }
        class C3 : IDisposable
        {
            C1 c1;
            C2 c2;
            public C3()
            {
                try {
                    c1 = new C1();
                    c2 = new C2();
                } catch {
                    this.Dispose();
                    throw new Exception();
                }
            }
            ~C3()
            {
                this.Dispose();
    
            }
            public void Dispose()
            {
                // basically an early deconstructor
                Console.WriteLine( "C3 Being Destroyed" );
                if ( c1 != null )
                    c1.Dispose();
                if ( c2 != null )
                    c2.Dispose();
                GC.SuppressFinalize(this);
                Console.WriteLine( "C3 Destroyed" );
            }
        }
        class MainClass
        {
            public static void Main(string[] args)
            {
                try {
                    using ( var c3 = new C3() )
                    {
                        Console.WriteLine("Rawr");
                    }
                } catch {
                    Console.WriteLine( "C3 Failed" );
                }
                GC.Collect();
            }
        }
    }
    
        4
  •  0
  •   Rune FS    15 年前

    在构造函数参数上做断言是第一步。断言任何后续逻辑都不会引发异常。也就是说,断言构造函数逻辑中使用的任何数据对于该特定用途都是有效的。像这样:

    c3(string someString)
    {
      Debug.Assert(!string.IsNullOrEmpty())
      c1 = new c1(someString);
    }
    

    如果空字符串会导致c1抛出异常。

    如果您无法确保输入验证不会引发异常。重写代码,这样你就可以了。因为那将是一种非常强烈的气味。如果引发异常的代码不是用户,而是供应商更改供应商。这将不是您在使用他们的代码时遇到的最后一个问题。

        5
  •  0
  •   Community frankie liuzzi    7 年前

    在“try”中包装构造函数体并在失败时调用Dispose可能是一件尽可能好的事情。不过,我建议不要使用“Try Catch”,而是使用“Try Finally”,并在“Try”末尾设置“ok”标志。如果在构造器期间抛出了一些东西,那么调试器将有机会在抛出时查看系统状态。

    看到我的问题了吗 Handling iDisposable in failed initializer or constructor