【问题标题】:How do I implement the dispose pattern in c# when wrapping an Interop COM Object?包装 Interop COM 对象时,如何在 c# 中实现 dispose 模式?
【发布时间】:2010-11-14 20:04:03
【问题描述】:

我的类包含一个来自 Interop 的对象,并在其上调用一个方法,使其分配东西。它还公开了一个释放这些东西的方法,所以我希望我应该在 Dispose() 中调用它:

class MyClass : IDisposable
{
    private DllName.ComClassName comInstance;

    void SomeMethod()
    {
        comInstance = new DllName.ComClassName();
        comInstance.AllocStuff();
    }

    public void Dispose()
    {
        comInstance.FreeThatStuff();
    }
}

现在,我应该扩展所有内容以遵循 Dispose 模式。我没有其他一次性或非托管资源要释放,所以假设 comInstance 是托管的(这不是 Interop 所做的,将非托管包装到托管中吗?),我认为该模式分解为:

public void Dispose()
{
    if (comInstance != null)
    {
        comInstance.FreeStuff();
        comInstance = null;
    }
}

除非我在 MyClass 的实例上显式调用 Dispose(),否则哪些泄漏会导致 Dispose 模式存在缺陷?这是否意味着 comInstance 必须是非托管的,并且模式分解为:

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

~MyClass()
{
    DisposeComInstance();
}

private void DisposeComInstance()
{
    if (comInstance != null)
    {
        comInstance.FreeStuff();
        comInstance = null;
    }
}

编辑:

  1. 为避免我的班级被完整的模式弄得杂乱无章,我可以直接封我的班级吗?
  2. 我怎么知道 ComClassName(通常是任何类)是非托管的?

【问题讨论】:

    标签: c# interop dispose


    【解决方案1】:

    最终你想要这种模式:

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    
    ~MyClass()
    {
        Dispose(false);
    }
    
    private void Dispose(bool disposing)
    {
        if (disposing)
        {
            // Dispose of disposable objects here
    
        }
    
        // Other unmanaged cleanup here which will be called by the finalizer
        if (comInstance != null)
        {
             comInstance.FreeStuff();
             comInstance = null;
        }
    
        // Call base dispose if inheriting from IDisposable class.
        base.Dispose(true);
    }
    

    有关原因的精彩文章,请查看Implementing IDisposable and the Dispose pattern properly.

    【讨论】:

    • -1,您需要将非托管对象移到 if (disposing) 块之外。非托管应始终清理。
    • 啊!你是对的,我从我的一些代码中复制了它,这些代码使用了具有非托管代码但实现了 dispose 的组件,并在 OP 的示例中进行了编辑......粘贴失败。固定。
    【解决方案2】:

    首先,我同意那些建议使用终结器作为备份的人,但尽量避免通过显式调用 myClass.Dispose 或通过“使用”来调用它。

    例如

    var myClass = new MyClass()
    try
    {
       //do stuff
    }
    finally
    {
       myClass.Dispose();
    }
    

    using (var myClass = new MyClass())
    {
       //do stuff
    }
    

    Marshall.ReleaseComObject

    如果您使用大量 COM 对象,我还建议您使用 Mashall.ReleaseComObject(comObj) 来显式清理对 RCW 的引用。

    所以,这样的代码建议在别处:

    if (comInstance != null)
    {
        comInstance.FreeStuff();
        comInstance = null;
    }
    

    会变成:

    if (comInstance != null)
    {
        comInstance.FreeStuff();
    
        int count = Marshall.ReleaseComObject(comInstance);
        if (count != 0)
        {
                Debug.Assert(false, "comInstance count = " + count);
                Marshal.FinalReleaseComObject(comInstance);
        }
    
        comInstance = null;
    }
    

    虽然检查 ReleaseComObject() 的返回值并不是绝对必要的,但我喜欢检查它以确保事情按预期递增/递减。

    两点规则

    如果您决定使用它,需要注意的是某些代码可能需要重构才能正确释放您的 COM 对象。特别是我所说的两点规则。任何使用包含 2 个点的 COM 对象的行都需要密切注意。例如,

    var name = myComObject.Address.Name;
    

    在此语句中,我们获得了对 Address COM 对象的引用,增加了它的 RCW 引用计数,但我们没有机会调用 ReleaseComObject。更好的方法是将其分解(try..finally 为清楚起见省略):

    var address = myComObject.Address;
    var name = address.Name;
    MyReleaseComObject(address);  
    

    其中 MyReleaseComObject 是一个实用方法,从上面包装了我的计数检查和 FinalReleaseComObject()。

    【讨论】:

    • 小心FinalReleaseComObject。取决于您从哪里获取 com 对象可能很危险。您最终可能会发现两个 com 对象实际上是相同的,而调用 FinalRelaseComObject 实际上会杀死两者。如果您随后使用其他 Com 对象,则会收到意外错误,这些错误并不总是很容易找到。
    【解决方案3】:

    看起来你几乎已经搞定了。我会回退到你有一个受保护的虚拟处理的模式,它接受一个布尔参数,指示是否应该处理托管项目。这样后面的人就会继续implement IDisposable properly

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }
    
    ~MyClass()
    {
        this.Dispose(false);
    }
    
    protected virtual void Dispose(bool disposing)
    {
        // if (disposing)
        // {
        //      // Managed
        // }
    
        if (comInstance != null)
        {
            comInstance.FreeStuff();
            comInstance = null;
        }
    
        // base.Dispose(disposing) if required
    }
    

    【讨论】:

    • 元注释:确保您的 Dispose(bool) 可以容忍连续多次调用。还要尽量减少抛出异常的可能性(并非总是可能)。
    • 元元注释——除此之外,在非托管清理期间永远不要获取锁或使用锁,这一点很重要。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-02
    相关资源
    最近更新 更多