有没有Dispose不会被调用'使用'块的情况?

这是我遇到的一个电话面试问题:是否有一段时间,Dispose不会被一个使用块声明范围的对象调用?

我的回答是否定的 – 即使在使用块中发生exception,Dispose仍将被调用。

面试官不同意并表示,如果using包裹在trycatch块中,则Dispose将不会在您进入catch块时调用。

这与我对这个构想的理解是相反的,而且我一直没能find任何支持面试官的观点。 他是对的还是我误解了这个问题?

 void Main() { try { using(var d = new MyDisposable()) { throw new Exception("Hello"); } } catch { "Exception caught.".Dump(); } } class MyDisposable : IDisposable { public void Dispose() { "Disposed".Dump(); } } 

这产生了:

 Disposed Exception caught 

所以我同意你的观点,而不是聪明的面试官。

有四件事情会导致Dispose在一个使用块中不被调用:

  1. 在使用块内部时,机器发生电源故障。
  2. 在使用区块内部,您的机器被primefaces弹熔化。
  3. 不可捕捉的exception,如StackOverflowExceptionAccessViolationException和其他可能的 exception 。
  4. Environment.FailFast

奇怪的是,我读到了一个情况,就是今天早上Dispose不会被调用。 在MSDN上签出这个博客 。 当你不迭代整个集合的时候,它使用了IEnumerable的Dispose和yield关键字。

不幸的是,这不涉及例外情况,说实话,我不知道那个。 我会期待它完成,但也许值得用一些代码检查?

关于电源故障的其他答案, Environment.FailFast() ,迭代器或通过using null东西作弊都是有趣的。 但是我发现好奇的是,没有人提到我认为是最常见的情况,即使在using Dispose()也不会被调用:当using内部的expression式抛出exception时。

当然,这是合乎逻辑的:在using的expression式抛出一个exception,所以分配没有发生,我们没有什么可以调用Dispose() 。 但是一次性对象已经可以存在,虽然它可以处于初始化状态的一半。 即使在这种状态下,它也可以拥有一些非托pipe资源。 这是正确实施一次性图案的另一个重要原因。

有问题的代码示例:

 using (var f = new Foo()) { // something } … class Foo : IDisposable { UnmanagedResource m_resource; public Foo() { // obtain m_resource throw new Exception(); } public void Dispose() { // release m_resource } } 

在这里,它看起来像Foo正确发布m_resource ,我们也正在using正确的。 但是Foo上的Dispose()并不会被调用,因为这个例外。 在这种情况下的修复是使用终结器,并在那里释放资源。

在现有的try块中, using块被编译器转换成它自己的try / finally块。

例如:

 try { using (MemoryStream ms = new MemoryStream()) throw new Exception(); } catch (Exception) { throw; } 

 .try { IL_0000: newobj instance void [mscorlib]System.IO.MemoryStream::.ctor() IL_0005: stloc.0 .try { IL_0006: newobj instance void [mscorlib]System.Exception::.ctor() IL_000b: throw } // end .try finally { IL_000c: ldloc.0 IL_000d: brfalse.s IL_0015 IL_000f: ldloc.0 IL_0010: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_0015: endfinally } // end handler } // end .try catch [mscorlib]System.Exception { IL_0016: pop IL_0017: rethrow } // end handler 

编译器不会重新排列东西。 所以会发生这样的事情:

  1. 抛出exception,或传播到using块的try部分
  2. 控制离开using块的try部分,并进入其finally部分
  3. 对象finally代码中的代码处理
  4. 控制离开finally块,exception传播到外面的try
  5. 控制离开外面的try ,并进入exception处理程序

点是,内部finally块总是在外部catch之前运行,因为exception不会传播到finally块结束。

这种情况不会发生的唯一正常情况是发生器(对不起,“迭代器”)。 一个迭代器变成一个半复杂的状态机, finally如果在yield return (但在它被丢弃之前)变得不可达,那么块不能保证运行。

 using (var d = new SomeDisposable()) { Environment.FailFast("no dispose"); } 

是的,有一种情况下,处置不会被称为…你在想这件事。 这种情况是当using块中的variables为null

 class foo { public static IDisposable factory() { return null; } } using (var disp = foo.factory()) { //do some stuff } 

不会抛出exception,但是如果在每种情况下都会调用dispose。 你的面试官提到的具体案例虽然是错误的。

面试官部分是对的。 Dispose可能无法正确地清理基础对象的个案基础上。

WCF例如有一些已知的问题,如果在使用块中抛出exception。 你的面试官可能正在想这个。

以下是MSDN上的一篇关于如何避免使用 WCF 的使用块的问题的文章。 这是微软官方的解决方法 ,虽然我现在认为这个答案和这个答案的组合是最优雅的方法。