是否存在不会为“使用”块调用 Dispose 的情况?

Posted

技术标签:

【中文标题】是否存在不会为“使用”块调用 Dispose 的情况?【英文标题】:Is there a situation in which Dispose won't be called for a 'using' block? 【发布时间】:2011-11-27 13:33:25 【问题描述】:

这是我的一个电话面试问题:有没有时候 Dispose 不会被一个对象调用,其范围由 using 块声明?

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

面试官不同意,说如果using被包裹在try-catch块中,那么在你进入catch块时不会调用Dispose。

这与我对结构的理解背道而驰,我无法找到任何支持面试官观点的东西。他是正确的还是我误解了这个问题?

【问题讨论】:

面试官错了;不要接受这份工作!最重要的是,您想为喜欢在周围留下地雷的人工作吗? 如果您有面试官的电子邮件地址,请给他发电子邮件,并向他展示证明调用了 dispose 的代码。如果他没有留下深刻的印象,请不要为他工作:) 接受这份工作!他们需要你! 也许您采访的是 Alex Papadimoulis 采访的同一个人? thedailywtf.com/Articles/My-Tales.aspx EMP 可以做到。一般断电也可能。 【参考方案1】:

会导致在 using 块中不调用 Dispose 的四件事:

    在 using 块内时您的机器出现电源故障。 您的机器在 using 块内部时被原子弹熔化。 Uncatchable exceptions 喜欢 ***ExceptionAccessViolationException 和 possibly others。 Environment.FailFast

【讨论】:

按概率排列;) 我们是否应该因为 #2 而用“放射性危害”标志标记每个使用块?请指教!!!1111en.wikipedia.org/wiki/File:Radioactive.svg *** 异常 - 很好的例子! 4. Environment.Failfast 下面列出。 msdn.microsoft.com/en-us/library/ms131100.aspx 也许 ExecutionEngineException 会破坏 finally 块,但测试起来相当复杂:-)【参考方案2】:
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 是可能的不要被叫。 @blindy:哇,这真的让你生气了,抱歉。你在招聘吗;-) @Blindy "面试官不同意并说 如果 using 被包装在 try catch 块中,那么在您进入 catch 块时将不会调用 Dispose。" 我认为答案是面试官“理论”的一个很好的反例。 请原谅,字符串上的 .Dump() 是什么? @Will,Øyvind 为我做的,为什么要重新输入?而且我从未尝试证明 Dispose 可能不会被调用,VdesmedT 做到了,他通过选择一个工作 示例进行了尝试。我相信我的观点是正确的。【参考方案3】:

奇怪的是,我今天早上读到了 Dispose 不会在 using 块中被调用的情况。在 MSDN 上查看此 blog。当您不迭代整个集合时,它与使用 Dispose 和 IEnumerable 和 yield 关键字有关。

不幸的是,这并没有处理异常情况,老实说,我不确定那个情况。我本来希望它会完成,但也许值得用一些快速的代码检查一下?

【讨论】:

帖子中描述的行为非常合乎逻辑。如果返回的迭代器的使用者没有在该IEnumerator 上调用Dispose,则不会调用迭代器(yield)方法中的 Dispose。或者换句话说:“在你不调用 Dispose 的情况下不会调用 Dispose”,这当然是微不足道的。解决方案:将 IEnumerator 的用法包装在 using 块中,或使用 C# foreach 语句对其进行迭代。【参考方案4】:

关于电源故障、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()。在这种情况下,解决方法是使用终结器并在那里释放资源。

【讨论】:

+1 非常不错的收获。实际上,我会说“修复”是根本不允许在 ctor 中分​​配非托管资源。对此类资源的所有访问都应使用延迟加载完成。 我第一次学习了在构造函数中不初始化对象的参数。我所知道的关于RAII 的一切都是错误的:( @HemalPandya,我认为 RAII 在 C++ 中更为重要,因为它没有垃圾收集器或using。但我不认为这是反对在构造函数中初始化的论点。如果您需要,它更像是正确实现终结器的一个论据。 没有必要在终结器中释放托管资源,因为它们将被(或可能已经被)GCed。如果他们反过来拥有一些非托管资源,他们应该在他们的终结器中处理它们。 这个答案特别有趣,因为这可能意味着面试官有些正确。我不同意该修复,我希望如果构造函数可以抛出该资源,则应在构造函数的 catch 块中释放该资源并重新抛出异常,因为无法保证是否/何时调用终结器。 【参考方案5】:

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 块完成。

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

【讨论】:

最终发生在 控件离开 try 块之后。 @Eric:我的意思是之前,因为我说的是外部尝试,而不是using 生成的尝试。但是当你同时谈论两者时,它会变得有点混乱。希望澄清一下。【参考方案6】:
using (var d = new SomeDisposable()) 
    Environment.FailFast("no dispose");

【讨论】:

如果您能提供更多信息,那就太好了:msdn.microsoft.com/en-us/library/ms131100.aspx【参考方案7】:

是的,有时不会调用 dispose ......你想多了。情况是 using 块中的变量为null

class foo

    public static IDisposable factory()
    
        return null;
    


using (var disp = foo.factory())

    //do some stuff

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

【讨论】:

【参考方案8】:

面试官部分正确。 Dispose 可能无法根据具体情况正确清理底层对象。

例如,如果在 using 块中引发异常,WCF 就有一些已知问题。你的面试官可能正在考虑这个问题。

这是来自 MSDN 的一篇关于如何使用 WCF avoid issues with the using block 的文章。这里是Microsoft's official workaround,虽然我现在认为这个答案和this one 的组合是最优雅的方法。

【讨论】:

以上是关于是否存在不会为“使用”块调用 Dispose 的情况?的主要内容,如果未能解决你的问题,请参考以下文章

在 Flutter 中,方法 'dispose' 被调用为 null

垃圾收集器是不是调用 Dispose()? [复制]

没有结果时,Firebase 查询不调用块

当传递到另一个对象时,谁应该在 IDisposable 对象上调用 Dispose?

关于 C# Dispose Pattern 的具体问题

不在 TPL Task 对象上调用 Dispose() 是不是可以接受?