C# 如果.ctor()抛出,是否将调用Dispose()?
我有一个类,其中一个C# 如果.ctor()抛出,是否将调用Dispose()?,c#,constructor,exception-handling,idisposable,C#,Constructor,Exception Handling,Idisposable,我有一个类,其中一个IDisposable成员变量在线初始化,另一个IDisposable在构造函数中初始化 如果构造函数抛出,是否将调用Dispose()?如果是这样,那么我认为有必要进行null检查。。。?如果不是,那么如何处置在线成员 sealed class SomeDisposable : IDisposable { ... } sealed class Foo : IDisposable { readonly SomeDisposable s
IDisposable
成员变量在线初始化,另一个IDisposable
在构造函数中初始化
如果构造函数抛出,是否将调用Dispose()
?如果是这样,那么我认为有必要进行null
检查。。。?如果不是,那么如何处置在线成员
sealed class SomeDisposable : IDisposable { ... }
sealed class Foo : IDisposable
{
readonly SomeDisposable sd1= new SomeDisposable(); // this doesn't throw
readonly SomeDisposable sd2;
public Foo()
{
sd2 = new SomeDisposable(); // assume this throws
// how does sd1 get Dispose()d?
}
public void Dispose()
{
sd1.Dispose();
if (sd2!= null) // is this null check necessary?
sd2.Dispose();
}
}
假设您正在表单中使用以下代码:
var foo = new Foo(someByteArray);
您的构造函数抛出一个异常,然后foo
将为null,因为类构造函数没有完成。任何试图调用它的Dispose
的操作都会导致发生NRE。有趣的问题
我试过这个:
try
{
//caller
using (var x = new Disposable1()) { }
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
public class Disposable1 : IDisposable
{
private Disposable2 first = new Disposable2();
private Disposable2 second;
public Disposable1()
{
second = new Disposable2("Goodbye!");
}
public void Dispose()
{
Debug.WriteLine("Disposable1.Dispose()");
first.Dispose();
if (second != null)
second.Dispose();
}
}
public class Disposable2 : IDisposable
{
public string Whatever { get; set; }
public Disposable2() { Whatever = "Hello!"; }
public Disposable2(string whatever)
{
Whatever = whatever;
throw new Exception("Doh!");
}
public void Dispose()
{
Debug.WriteLine("Disposable2.Dispose()" + Whatever);
}
}
…结果是
Doh!
因此,这两个成员似乎都没有被初始化或处置。目前在C#中,除了通过涉及
ThreadStatic
变量的恶意攻击之外,无法用一个内嵌的初始化器安全地初始化IDisposable
。构造函数必须通过工厂方法调用,该方法创建Disposition manager对象并在线程静态字段中存储引用。然后,字段初始值设定项可以将它们的值包装在对静态方法的调用中,该方法将它们添加到disposal manager对象中
实际的字段初始值设定项语法非常合理:
DisposalManager Cleaner = DisposalManager.CurrentManager;
DType1 DField1 = DisposalManager.Guard(new DType1);
DType2 DField2 = DisposalManager.Guard(new DType2);
处理
清理也是如此:
void Dispose(bool disposing)
{
Cleaner.Cleanup(disposing);
}
不幸的是,需要让对
Guard
的每个调用都自己访问线程静态字段,并且需要将所有构造函数调用包装在工厂方法中,这使得构造非常难看。太糟糕了,因为能够使用一行声明、创建和清理字段比必须在代码中的三个不同位置执行这些操作要好得多。CLR不会为您调用Dispose
,除非您通过显式调用它。您的outputStream
字段是私有的,不从外部引用,一旦GC开始收集,它将被收集。注意extream
可以初始化,因为它的初始化在类构造函数之前运行。否。你自己也不要加,灾难发生了。在地震将进程扔进大海后运行代码时,你无法推断会发生什么。在程序崩溃到桌面前一微秒处理任何东西都没有意义。请记住,处置始终是可选的。如果它造成的问题比它解决的问题多,那么你必须强烈避免使用它。这是因为发生了地震。一个C#语句可能会失败,原因有很多,绝大多数都非常非常糟糕,你不能假设你可以安全地继续运行代码。喜欢这种说法。从来没有发生过,但如果真的发生了,你永远不会想让诊断原因变得困难。你在地震后运行代码让事情变得困难。你没听见我说的话。这是一个如此普遍的问题,微软对此采取了一些措施,他们在CLR中添加了“关键异常”的概念。地震现在会无条件地终止你的程序,而不会给你抓住异常的机会。所以说吧,你再也不能把腿打掉了。您添加的代码永远不会被使用。这是因为异常发生后,Dispose
将从finally块中运行。但是您的代码示例不会重新编译代码执行。在异常传播之前,他从未分配过任何东西。这与实际情况不同。您的Disposable1
没有加入其构造函数。设置实际上是相同的。Disposable1构造函数中的这一行导致异常:second=newDisposable2(“再见!”);。我只是使用自定义类而不是流。@Colin这不是构造函数。这段代码实际上不会抛出任何异常。Daggnabbit!在我的程序中输入错误。“Dispose()”应该是“Disposable1()”。我更新了答案以反映真实的输出。@Dan:我只在VB.NET而不是C#中编写了这样的代码,并使用VB.NET字段初始值设定项的语义来避免线程静态变量的需要。我不认为这件事有什么特别困难的。最大的麻烦不是编写DisposalManager,而是处理所有工厂方法。通常情况下,工厂方法会调用构造函数,但这里有必要将派生类构造函数包装在基类工厂内的try/finally块中,这意味着最终需要两个派生类工厂……一个公共工厂,然后给基类工厂一个私有派生类工厂的委托,然后调用派生类构造函数,派生类构造函数又链接到基类构造函数。我怀疑有可能使用反射自动生成工厂类,这些工厂类以适当的顺序链接到适当的代码,并提供适当的保护,这样所有的丑陋都会局限于一个通用的工厂生成器类,但我不知道如何做到这一点。如果没有这一点,我不确定资源声明的清洁度是否能证明支持它所必需的丑陋。我怀疑有些人会觉得资源申报清洁度的改善证明了其他地方的成本是合理的,但我不知道我是否这样认为。合理的做法是为这样的事情提供适当的语言/框架,这样它就只需要在语言中实现一次,而不是在每个需要这种清理的类中实现一次。