有没有Dispose不会被调用'使用'块的情况?
这是我遇到的一个电话面试问题:是否有一段时间,Dispose不会被一个使用块声明范围的对象调用?
我的回答是否定的 – 即使在使用块中发生exception,Dispose仍将被调用。
面试官不同意并表示,如果using
包裹在try
– catch
块中,则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在一个使用块中不被调用:
- 在使用块内部时,机器发生电源故障。
- 在使用区块内部,您的机器被primefaces弹熔化。
- 不可捕捉的exception,如
StackOverflowException
,AccessViolationException
和其他可能的 exception 。 - 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
编译器不会重新排列东西。 所以会发生这样的事情:
- 抛出exception,或传播到
using
块的try
部分 - 控制离开
using
块的try
部分,并进入其finally
部分 - 对象由
finally
代码中的代码处理 - 控制离开finally块,exception传播到外面的
try
- 控制离开外面的
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 的使用块的问题的文章。 这是微软官方的解决方法 ,虽然我现在认为这个答案和这个答案的组合是最优雅的方法。