是否存在Dispose赢得';不要被要求;使用';块
本文关键字:使用 是否 Dispose 赢得 存在 | 更新日期: 2023-09-27 18:09:57
这是我的一个电话采访问题:对于作用域由using块声明的对象,是否有一段时间不会调用Dispose?
我的回答是否定的——即使在使用块期间发生异常,Dispose仍将被调用。
面试官不同意,并表示如果using
被封装在try
-catch
块中,那么在您进入catch块时将不会调用Dispose。
这与我对结构的理解相悖,我也找不到任何支持面试官观点的东西。他是对的,还是我误解了这个问题?
导致Dispose在使用块中不被调用的四件事:
- 在使用区块内时,您的机器出现电源故障
- 你的机器在使用块的内部被原子弹熔化了
- 像
StackOverflowException
、AccessViolationException
和可能的其他不可修补的异常 - 环境.FailFast
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不会在使用块中被调用的情况。在MSDN上查看此博客。当您不迭代整个集合时,可以将Dispose与IEnumerable和yield关键字一起使用。
不幸的是,这并不涉及例外情况,老实说,我不确定这一点。我本来希望它能完成,但也许它值得用一些快速的代码来检查一下?
关于电源故障、Environment.FailFast()
、迭代器或using
作弊的其他答案——也就是null
——都很有趣。但我感到奇怪的是,没有人提到我认为Dispose()
即使在using
存在的情况下也不会被调用的最常见情况:using
内部的表达式抛出异常。
当然,这是合乎逻辑的:using
中的表达式抛出了一个异常,所以赋值没有发生,也没有什么可以调用的Dispose()
。但是一次性对象可能已经存在,尽管它可能处于半初始化状态。即使在这种状态下,它也可能已经拥有一些非托管资源。这也是为什么正确实现一次性模式很重要的另一个原因。
问题代码示例:
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()
从未被调用。在这种情况下,修复方法是使用终结器并释放那里的资源。
编译器将using
块转换为自己的try
/finally
块,位于现有try
块内。
例如:
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
编译器不会重新排列东西。所以它是这样发生的:
- 异常被抛出或传播到
using
块的try
部分 - 控件离开
using
块的try
部分,进入其finally
部分 - 对象由
finally
块中的代码处理 - 控件离开finally块,异常传播到外部
try
- 控件离开外部
try
,进入异常处理程序
关键是,内部finally
块总是在外部catch
之前运行,因为在finally
块完成之前,异常不会传播。
唯一不会发生这种情况的正常情况是在生成器中(对不起,"迭代器"(。迭代器变成了一个半复杂的状态机,如果在yield return
之后(但在它被处理之前(它变得不可访问,那么finally
块就不能保证运行。
using (var d = new SomeDisposable()) {
Environment.FailFast("no dispose");
}
是的,有一种情况下不会调用dispose。。。你想得太多了。这种情况是当使用块中的变量是null
时
class foo
{
public static IDisposable factory()
{
return null;
}
}
using (var disp = foo.factory())
{
//do some stuff
}
不会抛出异常,但如果在所有情况下都调用了dispose,则会抛出异常。面试官提到的具体案例是错误的。
面试官说得有一部分是对的。CCD_ 37可能无法根据具体情况正确地清理底层对象。
例如,如果在使用块中引发异常,WCF会出现一些已知问题。你的面试官可能在想这个。
下面是MSDN上的一篇文章,介绍如何避免在WCF中使用块的问题。这是微软的官方解决方法,尽管我现在认为这个答案和这个答案相结合是最优雅的方法。