这是我在电话面试中遇到的问题:是否存在一种情况,即使用块声明作用域的对象不会调用Dispose?
我的答案是否定的-即使在using块期间出现异常,Dispose仍将被调用。
面试官不同意我的观点,并表示如果try-catch块包含using,则当您进入catch块时将不会调用Dispose。
这与我的理解相反,我没有找到任何支持面试官观点的东西。他是正确的还是我可能误解了问题?
这是我在电话面试中遇到的问题:是否存在一种情况,即使用块声明作用域的对象不会调用Dispose?
我的答案是否定的-即使在using块期间出现异常,Dispose仍将被调用。
面试官不同意我的观点,并表示如果try-catch块包含using,则当您进入catch块时将不会调用Dispose。
这与我的理解相反,我没有找到任何支持面试官观点的东西。他是正确的还是我可能误解了问题?
导致Dispose不会在using块中被调用的四件事:
StackOverflowException
,AccessViolationException
和可能的其他异常。DisposableThing
的构造函数抛出异常,那么using (var thing = new DisposableThing())
将无法清理它。虽然using
语句本身无法清理它,但不幸的是,DisposeableThing
也没有特别好的模式来确保自己的清理。更糟糕的是,如果vb.net或C#中派生类的构造函数抛出异常,那么可处理的基类几乎无法确保清理[C++/CLI将自动“Dispose”部分构造的对象,但vb.net和C#几乎强制放弃]。 - supercatvoid 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
不会被调用,VdesmedT尝试了并选择了一个可行的示例。我认为我的观点依然站得住脚。 - Blindy今天早上我偶然读到一篇文章,讲述在使用using块时Dispose方法不会被调用的情况。请看这篇MSDN博客,它涉及在使用IEnumerable和yield关键字时,当你没有迭代整个集合时如何使用Dispose。
不幸的是,这并没有处理异常情况,老实说我不太确定这种情况该怎么处理。我本以为会被处理掉,但也许值得通过一小段代码来检查一下?
Dispose
,只有当返回的迭代器的消费者调用IEnumerator
上的Dispose
方法,该方法才会被调用。换句话说:“如果不调用Dispose,就不会调用Dispose”,这显然是微不足道的。解决方法是:将对IEnumerator
的使用封装在using
块中,或者使用C#的foreach
语句进行迭代。 - Steven其他回答关于电源故障、Environment.FailFast()
、迭代器或通过使用 null
的东西来作弊都很有趣。但我发现很奇怪,没有人提到我认为是最常见的情况,即使在 using
存在的情况下也不会调用 Dispose()
:当 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
。但我不认为这是反对在构造函数中进行初始化的论点。如果需要,这更多地是一个正确实现终结器的论点。 - svickusing
块被编译器转换为一个独立的 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。但是当你同时谈论两者时,会有点混淆。希望我澄清了一些。 - cHaousing (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,这个代码不会引发异常。但是你的面试官提到的特定情况是错误的。
Dispose
可能无法正确清理底层对象。