C#自动处理IDisposable对象的内存释放吗?

4
我正在编写一个ASP.NET Web API,其中有一个类需要实现IDisposable接口以确保管理的资源被释放,在测试期间我发现这一点。因此,我在我的类中实现了IDisposable,并将释放资源所需的代码放在Dispose()方法中。
我的代码中有许多地方(数百个),在这些地方我创建了这个对象的实例,并在同一行调用新实例上的方法。我只需要实例来调用单个方法。
以下是一个示例:
// MyObject, a class that needs to be disposed of.
public class MyObject : IDisposable
{
    private AnObjectThatMustBeDisposed localObject;

    public MyObject() 
    {
        localObject = SomeLibrary.SomeProject.AnObjectThatMustBeDisposed.Create();
    }

    public void doOperationOne()
    {
        localObject.DoSomething(1);
    }

    public string getOperationTwo()
    {
        return localObject.DoSomething(2);
    }

    public string getOperationThree()
    {
        return localObject.DoSomething(3);
    }

    public bool getOperationFour(string input)
    {
        return localObject.DoSomethingSpecial(4,input.ToLower());
    }

    ...

    public void getOperationOneHundred(DateTime input)
    {
        localObject.DoSomethingElse(100,input);
    }

    public void Dispose()
    {
        localObject.CloseResources();
        localObject.FreeUpMemory();
        localObject.Close();
        localObject.Dispose();
    }
}

// A class that makes use of MyObject
public class MyLibraryThatUsesMyObject
{
    public void Method1()
    {
        new MyObject().doOperationOne();
    }
    public string Method2()
    {
        return new MyObject().getOperationTwo();
    }
    public int Method3()
    {
        return new MyObject().getOperationThree();
    }
    public bool Method4(string testString)
    {
        if (testString.Length > 6)
        {
            if (new MyObject().getOperationFour(testString)) return true;
            else return false;
        }
        else return false;
    }

    ...

    public void Method100()
    {
        new MyObject().doOperationOneHundred(DateTime.Now);
    }
}

我的问题是:当.NET对象超出其作用域时,是否会自动 Dispose()?还是我必须手动进行操作...
public void Method1() 
{
    using (MyObject o = new MyObject())
    {
        o.DoOperationOne();
    }
}

如果我只有两个或三个方法,那么这个重构并不困难。但如果我有很多方法,这个重构可能需要很长时间。

我不确定ASP.NET在处理请求完成后如何处理 - 即框架是否会给代码时间Dispose()一些东西,还是一旦调用return,就立即“切断”执行,不让它释放资源?

事实上,在没有自己实现IDisposable的情况下,由于未释放资源导致泄漏,MyObject类内部的东西无法正常工作,这表明.NET并没有自动Dispose东西。因此,如果是这种情况,我能做些什么来避免必须重构数百个方法吗?


编辑:我尝试了简单地实现IDisposable,但我的单元测试仍然能够产生资源泄漏。因此,我的怀疑是.NET不会自动释放,现在我的问题变成了 - 如何强制释放而不必重构数百个方法?


2
实际上,Dispose 是用于释放非托管资源的,而不是托管资源。这就是为什么你必须手动调用它的原因。 - Kirill Polishchuk
@kirill 不,那不是真的。实现 IDisposable 接口的每个类都是托管对象,仍然需要被释放。也许它内部使用了非托管代码,但这取决于类本身。 - CodingYoshi
@CodingYoshi --> https://msdn.microsoft.com/zh-cn/library/fs2xkftw(v=vs.110).aspx - Kirill Polishchuk
@KirillPolishchuk 我读了很多文章,这是微软的错误建议。我认为你甚至不会同意这个建议。例如,文章中的第一行说:“您实现Dispose方法以释放应用程序使用的非托管资源。”。所以如果我正在使用StreamReaderSqlConnection(托管资源),我就不需要实现IDisposable吗?显然这是不正确的。如果我将它们作为我的类的字段,那该怎么办?不确定他们为什么会这样说... - CodingYoshi
IDisposable 单独使用没有用处,你还应该使用 using(...) 语句。 - slugster
显示剩余3条评论
3个回答

7

Dispose方法不会自动调用。如果您没有显式地调用.Dispose() (或通过using语句),则该方法永远不会被调用。

唯一需要注意的是使用该模式实现的方法:

public void Dispose()
{
    GC.SuppressFinalize(this);
    Dispose(true);
}

~MyClass()
{
    Dispose(false);
}

bool _isDisposed = false;
protected virtual void Dispose(bool disposeing)
{
    if(_isDisposed)
        return;

    _isDisposed = true;

    if(disposing)
    {
        //Disposed managed code here
    }

    //Dispose unmanaged code only here.
}

当对象被终结时,将调用Dispose(false),但是当disposingfalse时,您不允许处理(或甚至访问)托管对象(即:其他实现了.Dispose()的东西)。

如果您想正确地释放资源,则需要重构代码。

Stephen Cleary撰写了一篇非常好的文章"IDisposable: What Your Mother Never Told You About Resource Deallocation",该文章非常好地解释了Dispose的工作原理以及如何正确编写自己的可处理对象(例如,上述警告模式被Microsoft推荐,但实际上是一个非常糟糕的模式。类应该只包含托管资源或派生自SafeHandle并仅包含未管理的资源和可能的其他SafeHandles。您不应该有一个同时持有托管和未管理资源的类,也不应该有一个单个类持有多个未管理资源)。


“你不应该有一个同时持有托管和非托管资源的类”——我第一次听说这个。有没有更详细的阅读建议? - CodingYoshi
@CodingYoshi 这个建议来自我链接的文章,特别是标题为“通过利用可处置设计原则将 IDisposable 的使用案例最小化来解决 IDisposable 的困难”的部分。 - Scott Chamberlain

3
我不确定ASP.NET如何处理请求完成的情况 - 即框架是否给代码时间来Dispose()一些东西,还是在调用返回时立即“切断”执行,不让东西Dispose?我的回答将尝试回答上面的具体问题,因为你已经得到了其他问题的答案。在MVC框架中,有一个创建控制器的类,名为DefaultControllerFactory,它有这个方法:
public virtual void ReleaseController(IController controller)
{
    IDisposable disposable = controller as IDisposable;
    if (disposable != null)
    {
        disposable.Dispose();
    }

}

请注意上述方法所需的参数:IController。因此,它会传递您的控制器类(由于所有控制器都通过派生BaseController实现该接口),如果您的控制器实现了IDisposable,它将对其进行转换然后调用Dispose()。这样,您就有机会做任何需要清理的工作。

如果您自己编写了工厂并且没有使用DefaultControllerFactory,则确保您正在执行相同的操作,并在控制器上调用Dispose

我从MVC的源代码中获取了上述源代码。


我怀疑这种行为很可能是这样的,感谢您发布这个具体内容。 - PSGuy

0
不,当对象超出范围时,CLR永远不会调用您的对象上的Dispose。但是,它将调用类析构函数(必须显式地覆盖)。
注意:以下示例不旨在演示生产代码中应该执行的操作。Microsoft推荐的做法可以在此处找到。
考虑以下类:
public class MyDisposable : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Disposing this disposable instance.");
    }

    // Note: This is not the right way to implement the dispose pattern. See
    // the MSDN docs on the recommended pattern.
    ~MyDisposable()
    {
        Dispose();
    }
}

现在,编写使用它的方法,并在 MyDispoable.Dispose 方法上设置断点。
private static void UseDisposable()
{
    Console.WriteLine("Enter");
    var disposable = new MyDisposable();
    Console.WriteLine("Exit");
}

你会发现,当离开这个作用域时,不仅 MyDisposable 上的 Dispose 没有被调用,而且 finalizer 也没有被调用。这意味着你在方法中创建的局部变量在方法结束时并没有被清理,即使它们是方法的局部变量(没有返回给调用者或分配给其他对象)。
我自己编写代码时管理可释放资源的方式是不将其用作类成员,而是将所有可释放资源放在 using 块中。但请记住,using 只是 try {...} finally {...} 的语法糖。
现在,我认为你的问题更多地涉及 ASP.NET 而非 CLR。我不确定 ASP.NET 如何处理可释放资源。如果你在 Controller 中使用该资源,则尝试实现 IDisposable,并使用断点查看是否在方法完成后调用了它。这是一个我不太确定的场景,但似乎这样做是合理的。
编辑:请参考 @CodingYoshi 关于 ASP.NET Controller 处置的答案。原来 MVC 将确定性地调用 dispose,因此你的一些重构可能会更简单。

在析构函数方法中,您可以做的事情非常有限制,仅仅在其中直接调用Dispose()是一个非常不安全的建议。 - Scott Chamberlain
当然。这只是为了演示而非真正的实现。重点在于展示C#中资源不会因为范围改变而被本质地释放/收集。 - PSGuy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接