我不太了解使用/ 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#管理得更好,因为它的初始化程序的工作方式。