构造函数异常对象中的异常

Ser*_*kov 8 c#

考虑以下课程:

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
}
Run Code Online (Sandbox Code Playgroud)

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

using (var c3 = new C3())
{
}
Run Code Online (Sandbox Code Playgroud)

这个代码片段怎么样?

在这种情况下,我们不能调用Dispose方法,因为我们的对象永远不存在.

我们知道,在这种情况下会调用终结器,但是我们只能处理CriticalFinilizedObjects而我们不能处理C1或C2对象.

我的解决方案非常简单:

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;
}
Run Code Online (Sandbox Code Playgroud)

这个解决方案可能在某些细节上有所不同,但我认为您可以理解关键原则:如果构造函数抛出异常,我们会强制释放获取的资源并抑制最终化.

有任何其他的idias,建议或更好的解决方案?

PS Herb Sutter在他的博文(http://herbsutter.wordpress.com/2008/07/25/constructor-exceptions-in-cc-and-java/)中提出了这个问题,但他没有提出解决方案.

Gre*_*ech 8

你提出的是我在考虑这个问题时得出的结论.

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

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

我认为这是一个相当罕见的情况 - 通常一个包装一次性资源的类将把它作为构造函数参数而不是自己创建它.

  • 双步初始化问题可能是问题的根源,从一个点上你有一个实际上不可用的对象的事实开始,你怎么能防止不正当使用?(检查每个方法/属性?)代码更易于维护.我知道双重构造在哪里惯用的唯一地方是Symbian C++,但这是因为编译器没有正确处理构造中的异常,所以它更像是一个系统范围的黑客而不是解决方案. (4认同)

Sco*_*hic -1

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