在包装Interop COM对象时,如何在c#中实现dispose模式?

我的类包含来自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. 为了避免整个模式让我的class级混乱,我可以封闭我的class级吗?
  2. 我怎么知道ComClassName(通常是任何类)是不受管理的?

看起来你几乎已经钉了它。 我会回到你有一个受保护的虚拟Disposing的模式,它采用一个布尔参数来指示是否应该处理被管理的项目。 这样,有人会跟你一起继续正确地实施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 } 

最终你需要这种模式:

 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模式。

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

例如

 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点规则。 使用包含2个点的COM对象的任何行都需要密切关注。 例如,

 var name = myComObject.Address.Name; 

在这个语句中,我们得到一个对Address COM对象的引用,增加了它的RCW引用计数,但我们没有机会调用ReleaseComObject。 更好的方法是将其分解(尝试…为清晰起见省略):

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

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