我不太了解使用/ Disposable对象的工作原理

我问了一个关于从函数中返回一个Disposable( IDisposable )对象的问题 ,但我认为如果我在那里提出这个问题,我会讨论这个问题。

我创建了一些示例代码:

 class UsingTest { public class Disposable : IDisposable { public void Dispose() { var i = 0; i++; } } public static Disposable GetDisposable(bool error) { var obj = new Disposable(); if (error) throw new Exception("Error!"); return obj; } } 

我故意用这种方式编码,因为我打电话给:

 using (var tmp = UsingTest.GetDisposable(true)) { } 

使用调试器,我注意到Dispose方法永远不会执行,即使我们已经实例化了一个Disposable对象。 如果我正确理解Dispose的目的,如果这个类实际上已经打开了句柄等,那么我们就不会在完成它们后立即关闭它们。

我问这个问题,因为这种行为符合我的预期,但在相关问题的答案中,人们似乎表明using会照顾一切。

如果using仍然以某种方式照顾所有这一切,有人可以解释我错过了什么? 但是,如果这段代码确实会导致资源泄漏,你会如何建议我编写GetDisposable代码(条件是我必须实例化IDisposable对象并运行可能在return语句之前抛出exception的代码)?

根据GetDisposable语义,这可能是我实现它的方式:

 public static Disposable GetDisposable(bool error) { var obj = new Disposable(); try { if (error) throw new Exception("Error!"); return obj; } catch (Exception) { obj.Dispose(); throw; } } 

它从未被调用的原因是因为你分配它的方式。 永远不会分配“tmp”变量,因为GetDisposable(bool)函数永远不会返回,因为您抛出了exception。

如果你要说的话,

 using (var tmp = new Disposable()) { throw new ArgumentException("Blah"); } 

那么你会看到IDisposable::Dispose() 确实被调用了。

要理解的基本要点是using块必须获得对IDisposable对象的有效引用。 如果发生某些exception使得在using块中声明的变量没有被分配,那么你运气不好,因为using块将不知道IDisposable对象。

至于从函数返回一个IDisposable对象,你应该使用函数内部的标准catch块来在发生故障时调用Dispose() ,但显然你不应该使用using块,因为这会在你之前处理对象准备好自己这样做。

这是因为永远不会分配tmp变量。 使用一次性物品需要注意的事项。 GewtDisposable的更好定义是:

 public static Disposable GetDisposable(bool error) { var obj = new Disposable(); try { if (error) throw new Exception("Error!"); return obj; } catch { obj.Dispose(); throw; } } 

因为它确保obj被处置。

IDisposable接口只是保证实现它的类具有Dispose方法。 它对调用此方法没有任何作用。 当块退出时,using块将在对象上调用Dispose。

您在GetDisposable创建一个IDisposable ,但由于您通过抛出exception退出该函数,它永远不会被返回,因此永远不会分配tmp 。 using语句是简写

 var tmp = UsingTest.GetDisposable(true); try { } finally { if(tmp != null) tmp.Dispose(); } 

而你永远不会到达try块。 您的示例中的解决方案是在创建一次性obj之前检查error标志:

 public static Disposable GetDisposable(bool error) { if (error) throw new Exception("Error!"); return new Disposable(); } 

一个相关的问题是在失败的初始化程序或构造函数中处理iDisposable,我认为答案是如果你想避免从失败的构造函数中泄漏一次性对象,你将不得不从构造函数中走私对象的副本(例如存储它)在传入的容器中,或将其分配给传递的引用变量)并将构造函数调用包装在catch块中。 Icky,但我不知道怎么做得更好。 VB.net实际上可以比C#管理得更好,因为它的初始化程序的工作方式。