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

时间:2010-11-19 00:14:09

标签: c# idisposable using-statement

我问了一个关于returning a Disposable (IDisposable) object from a function的问题,但我认为如果我在那里提出这个问题,我会讨论这个问题。

我创建了一些示例代码:

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语句之前抛出异常的代码)?

6 个答案:

答案 0 :(得分:11)

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

如果您要改为说明以下内容,

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

然后您会看到IDisposable::Dispose() 确实被调用。

要理解的基本要点是using块必须获得对IDisposable对象的有效引用。如果发生某些异常以致using块中声明的变量未被分配,那么您运气不好,因为using块将不知道IDisposable对象。 / p>

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

答案 1 :(得分:4)

根据您对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;
    }
}

答案 2 :(得分:3)

这是因为永远不会分配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的处理。

答案 3 :(得分:1)

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

答案 4 :(得分:1)

您在IDisposable中创建GetDisposable但由于您通过抛出异常退出该函数,因此永远不会返回该函数,因此永远不会分配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();
}

答案 5 :(得分:0)

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