正确使用IDisposable与托管成员

时间:2015-02-25 06:17:51

标签: c# resources garbage-collection unmanaged idisposable

如果成员实现IDisposable,那么IDisposable是否应该在链中一直实现?

例如,在下面的例子中,DbClass打开一个SqlConnection(实现IDisposable)。那么两者 DbClass和BusinessLogic应该实现IDisposable吗?

这基本上是C#版本的RAII /析构函数吗?

我是否需要(或在最佳实践的情况下,我应该实施终结器?)

我很难找到正确的方法,所以提前谢谢。

class Program
{
    private BusinessLogic bl;

    static void Main(string[] args)
    {
        BusinessLogic bl = new BusinessLogic();
    }
}

class BusinessLogic
{
    public DbClass _db;

    public BusinessLogic()
    {
        _db = new DbClass();
    }
}

class DbClass
{
    private SqlConnection _conn;

    public DbClass()
    {
        _conn = new SqlConnection("connection string");
        _conn.Open();
    }
}

3 个答案:

答案 0 :(得分:0)

首先,让我们将实现IDisposable的任何类型定义为托管资源

关于终结者。

由于您没有非托管资源(文件或套接字句柄,指针等),因此您不需要实现终结器,因为终结器用于非托管资源清理。

实际上,在.NET 2.0中出现SafeHandle之后,非常罕见的情况是,应该使用非托管资源(包含在SafeHandle中的所有内容都是托管资源)。

以下解释假设您仅处理托管资源。

关于IDisposable实施。

如果某些类型的拥有托管资源,那么它也必须处置它 如果每个操作都创建了托管资源,则必须在操作完成后将其处理:

class A
{
    void Method1()
    {
        using (var connection = new SqlConnection(/* ... */))
        {
            // load data from database
        } // the resource (connection) will be disposed here
    }
}

请注意,您不需要A来实施IDisposable

如果托管资源与其所有者一样长,那么所有者必须实施IDisposable并且必须在其Dispose中配置托管资源:

sealed class B : IDisposable
{
    private readonly SqlConnection _conn;
    private bool _isDisposed;

    public B()
    {
        _conn = new SqlConnection(/* ... */);
    }

    public void Dispose()
    {
        if (_isDisposed)
            return;

        if (_conn != null)
            _conn.Dispose();

        _isDisposed = true;
    }
}

因此,如果class C的实例以相同的方式拥有B实例,那么它也必须实现IDisposable,依此类推。以下Dispose实施sealed和非sealed类型之间略有不同:对于非密封类型,您应该像这样实施Dispose

    public void Dispose()
    {
        if (_isDisposed)
            return;

        // dispose managed resources of base type
        // ...
        OnDispose();

        _isDisposed = true;
    }

    protected virtual void OnDispose()
    {
    }

如果某些类型不拥有托管资源,则不得将其丢弃:

class D // do not implement IDisposable, since we don't own connection
{
    private readonly SqlConnection _conn;

    public D(SqlConnection conn)
    {
        // connection is created elsewhere, and will be used by another objects
        _conn = conn;
    }

    public void Method2()
    {
        //  read data from database using connnection...
    }
}

答案 1 :(得分:0)

首先考虑您的DbClass是否应该拥有SqlConnection成员。另一种方法是DbClass的每个方法都需要连接以在using语句中包含本地SqlConnection。当方法退出(干净地或异常)时,SqlConnection将从处于使用状态 这意味着 一个。当你不需要它们时,你永远不会长时间保持开放式连接 湾您的任何课程都不再需要实施IDisposable 这是我的(有限的)承诺,这是使用连接的推荐方式,并且它可以更好地扩展。

答案 2 :(得分:0)

要理解Dispose,必须了解对象拥有资源意味着什么;这个概念实际上非常简单,但微软从未真正定义它。

对象获取资源,当它要求实体在其自身之外进行维护,直到进一步通知一个对该对象有益但可能对其他实体有害的状态。对象在向上述外部实体通知不再需要其服务时,从而释放资源

如果代码创建了一个File对象,该对象的构造函数将要求外部实体(OS文件管理器)独占使用文件。这将使File对象受益,但对可能要使用该文件的任何其他内容都是有害的。在Dispose对象上调用File将导致它通知操作系统文件管理器它不再需要该文件,因此文件管理器可以将其提供给其他实体。

如果某个其他类FooFormatter创建了一个File对象并将其存储在一个字段中,则创建File的行为将对文件类说“请获取对该文件的独占访问权限”。一个具有此名称的文件,并将其提供给我[主叫代码],直至另行通知“。此时,FooFormatter会要求File建立对FooFormatter有利但对其他一切都不利的状态。此类行动将代表获取资源,并且在采取此类行动时,FooFormatter将接受在File不再需要时通知File的义务。当IDisposable收到此类通知时,将依次通知操作系统文件管理器。

请注意,获取资源的某些(但不是全部).NET对象会要求运行时通知它们,如果它们似乎被放弃,并且如果通知则会尝试释放资源。这是一个名为“终结”的过程;由这些对象封装的资源称为“托管资源”;任何类型的资源如果其所有者被遗弃将不会被自动清理是“非托管资源”。请注意,实现IDisposable并不意味着某些东西是托管资源,因为许多对象不会(并且在某些情况下不能)实现清理其资源的终结器。由于无法保证及时性或成功,因此几乎永远不应该依赖最终定稿;在大多数情况下,最好假装没有这样的机制存在,并将放弃资源保持对象的代码视为已损坏。

关于具有Dispose字段的对象是否应该在Dispose上实现IDisposable并在这些字段上调用Dispose的$ 50,000问题是这些字段标识的对象是否维护为非为了持有字段的对象的利益而处置状态 。如果答案是肯定的,那么持有Dispose对象的对象应该实现{{1}},因此它会在嵌套对象上调用{{1}}。如果答案是否定的 - 字段标识某些其他对象拥有的对象 - 则不应丢弃这些字段。