5

我的类包含一个来自 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(通常是任何类)是非托管的?
4

3 回答 3

3

最终你想要这种模式:

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);
}

有关原因的精彩文章,请查看正确实施 IDisposable 和 Dispose 模式。

于 2009-07-28T19:31:36.973 回答
3

首先,我同意那些建议使用终结器作为备份的人,但尽量避免通过显式调用 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() 的返回值并不是绝对必要的,但我喜欢检查它以确保事情按预期递增/递减。

2点规则

如果您决定使用它,需要注意的是某些代码可能需要重构才能正确释放您的 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()。

于 2009-07-29T04:20:27.720 回答
2

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

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
}
于 2009-07-28T19:25:56.500 回答