是否存在Dispose赢得';不要被要求;使用';块

本文关键字:使用 是否 Dispose 赢得 存在 | 更新日期: 2023-09-27 18:09:57

这是我的一个电话采访问题:对于作用域由using块声明的对象,是否有一段时间不会调用Dispose?

我的回答是否定的——即使在使用块期间发生异常,Dispose仍将被调用。

面试官不同意,并表示如果using被封装在try-catch块中,那么在您进入catch块时将不会调用Dispose。

这与我对结构的理解相悖,我也找不到任何支持面试官观点的东西。他是对的,还是我误解了这个问题?

是否存在Dispose赢得';不要被要求;使用';块

导致Dispose在使用块中不被调用的四件事:

  1. 在使用区块内时,您的机器出现电源故障
  2. 你的机器在使用块的内部被原子弹熔化了
  3. StackOverflowExceptionAccessViolationException和可能的其他不可修补的异常
  4. 环境.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

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

  1. 异常被抛出或传播到using块的try部分
  2. 控件离开using块的try部分,进入其finally部分
  3. 对象由finally块中的代码处理
  4. 控件离开finally块,异常传播到外部try
  5. 控件离开外部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中使用块的问题。这是微软的官方解决方法,尽管我现在认为这个答案和这个答案相结合是最优雅的方法。