【问题标题】:Class Derived from a Disposable Class having Disposable Member类派生自具有一次性成员的一次性类
【发布时间】:2012-10-12 16:35:27
【问题描述】:

我有一些关于一次性课程的问题。假设我有一个 IDisposable 实现类,其中包含一些一次性成员。我已经实现了Dispose() 方法,即:

class BaseCustom: IDisposable
{
    private System.Net.Sockets.TcpClient tc;
    private System.Net.Sockets.NetworkStream ns;
    public string str;
    public int i;

    public BaseCustom(string host, int port)
    {
        tc = new System.Net.Sockets.TcpClient(host, port);
        ns = tc.GetStream();
    }

    // some other methods work on members (i, str, tc, ns)

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (ns != null)
            {
                ns.Close();
                ns = null;
            }
            if (tc != null)
            {
                tc.Close();
                tc = null;
            }
        }
    }

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

Q1) 既然没有非托管资源,那么压制finalizer可以吗? (在此处的代码中阅读note

Q2) 既然我们处理了一次性成员并抑制了 GC,那么这里的 intstring 成员会发生什么?我们是否也需要处理它们?

Q3) 目前对tcns 的释放是否正确?我看到不同版本的 .NET 引用中关于调用 Close()Dispose() 的差异

现在假设我们有一个派生类:

class DerivedCustom : BaseCustom
{
    public string cstr;
    public int ci;

    public DerivedCustom(string host, int port)
        : base(host, port)
    {}

    // some extra methods
}

Q4)可能与Q2有关;我们需要在这里覆盖任何Dispose()s 吗?我们不会在派生类中引入任何非托管或一次性资源。保持原样是否安全(即信任基地的处置机制)?如果 GC 被抑制(它在派生中也被抑制,对吧),cicstr 会发生什么?同样与 Q1 相关,我们是否需要任何终结器?

Q5)如果基类是抽象类怎么办?

Q6)这很有趣;原样的代码没有给出关于 FxCop 1.36 中可处置性的任何警告。但是,如果我将一次性成员添加到 DerivedCustom 并且 仍然不覆盖一次性方法(因此不处理新成员),例如:

class DerivedCustom : BaseCustom
{
    public string cstr;
    public int ci;
    // below is the only extra line
    public System.Net.Sockets.TcpClient ctc = new System.Net.Sockets.TcpClient("ho.st", 1234);

    public DerivedCustom(string host, int port)
        : base(host, port)
    {}

    // some extra methods
}

在 FxCop 中仍然没有收到任何警告。这让我有点吃惊,因为处理 ctc 似乎没有得到妥善处理。

【问题讨论】:

    标签: c# garbage-collection idisposable tcpclient


    【解决方案1】:

    A1) 如果没有非托管资源,请不要在类中添加终结器。终结器对于清理非托管资源很有用; GC 将自行处理托管资源。

    A2) SuppressFinalize 不会阻止对象被垃圾回收;它只会阻止 GC 调用对象的终结器。未装箱的整数永远不会被垃圾收集,因为它们是值类型;字符串将像往常一样被垃圾收集。

    A3) 我会调用 Dispose,而不是 Close,因为 Dispose 保证具有 Dispose 语义,而 Close 可能会做一些稍微不同的事情。

    A4) 如果派生类没有添加新的处置要求,请不要重写 Dispose 方法。

    A5) 如果基类是抽象类,那也没什么区别。

    A6) 我对 FXCop 了解不够,无法回答,抱歉。

    此外,没有理由将字段设置为空。例如,在没有非托管资源的情况下,Dispose 方法应如下所示(编辑:重新阅读问题中链接的 FXCop 页面后,我要补充一点,在这种情况下,该类应该被密封):

    public void Dispose() 
    { 
        if (ns != null) 
        { 
            ns.Dispose(); 
        } 
        if (tc != null) 
        { 
            tc.Dispose(); 
        } 
    } 
    

    但是,如果该类可能具有包含非托管资源的派生类型,则您应该坚持使用经典模式,并根据您的示例进行一些修改:

    protected virtual void Dispose(bool disposing)         
    {         
        if (disposing)         
        {         
            if (ns != null)         
            {         
                ns.Dispose();         
            }         
            if (tc != null)         
            {         
                tc.Dispose();         
            }         
        }         
    }         
    
    public void Dispose()         
    {         
        Dispose(true);         
        GC.SuppressFinalize(this);         
    }         
    

    【讨论】:

    • +1。优秀的答案。不过,我要澄清一下,因为 ints 是值类型,所以当它们所在的对象被垃圾回收时,它们消耗的空间会自动删除。
    • 对于A5,也不需要检查它们是否是null - 构造函数会保证它们被设置。只需连续运行两个Dispose()es,无需检查或分配。
    • @JesseC.Slicer 好点,但这可能是一个好习惯,因为这只是 this 类的不变量;在其他情况下,可能需要进行空检查。
    【解决方案2】:

    Q1:是的。

    Q2:不。它们是 100% 托管类型,GC 会处理它们。 IDisposable 用于在继承或组合的某处需要处理非托管资源的类型。比如tcns

    Q3:致电Dispose()。这是惯用的,一个好的实现会调用它自己的Close()

    Q4:不,不需要覆盖。你没有在任何地方抑制 GC - 你正在抑制 GC 运行 finalizers 因为你已经运行了Dispose()

    Q5:与 Q4 没有区别。

    Q6:是的,FxCop 在其IDisposable 规则方面存在误报和误报。遵循最佳实践并忽略 FxCop 中看似可疑的内容,您会没事的。

    【讨论】:

      【解决方案3】:

      您应该考虑在实现 IDisposable 接口的基类中实现终结器(Dispose Pattern)。

      IDisposable 接口用于释放本机资源。 Int32String 由资源管理并由 GC 收集。

      如果类使用非托管/本机资源,则基类是抽象类还是具体类都没有关系。

      即使派生类没有定义任何新资源但类继承了基类功能,这意味着派生类对象将使用本机资源。即使您跳过在派生类中声明终结器但在基类中定义了终结器,基类终结器也足以指示 GC 在内存收集期间运行终结器。

      调用GC.SuppressFinalize 是可以的(实际上必须),因为它会从 F-Reachable 队列中删除条目并帮助 GC 在第一次扫描时收集对象内存。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-02-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多