C#重赋值COM对象前是否需要先释放原有COM对象?
结论
重赋值COM对象前必须先释放原有实例,你修改后的实现是正确的,原实现存在严重的非托管资源泄漏问题。
原因说明
COM对象的生命周期由原生的引用计数机制管理,.NET 运行时创建的 RCW(运行时可调用包装)只会在两种情况下减少对应COM对象的引用计数:
- 开发者显式调用
Marshal.ReleaseComObject/Marshal.FinalReleaseComObject - RCW实例被GC回收时自动执行释放操作
原实现中每次调用ReAssign()直接覆盖MyObject引用时,旧的COM对象对应的RCW并没有被立刻回收,对应的COM对象引用计数也不会减少,这些被替换的旧COM实例会一直驻留在内存中,直到整个DisposeTest实例被GC回收才会批量处理,甚至会出现永久泄漏的情况(比如COM对象自身存在循环引用、或者进程长期运行GC迟迟不触发回收)。如果你的COM对象占用了文件句柄、网络连接、互斥锁这类稀缺资源,泄漏带来的影响会更为明显。
补充优化建议
除了重赋值前释放原有对象,你还可以做两点优化来规避潜在问题:
- 释放完原有COM对象后,先把引用置为
null,避免后续逻辑意外访问已释放的对象 - 如果存在多线程并发调用
ReAssign()、Dispose()的场景,需要加锁保护对MyObject的访问,避免重复释放、空引用、访问已释放对象等问题
优化后的实现参考:
private readonly object _lockObj = new object(); public void ReAssign() { lock (_lockObj) { // 释放当前持有的COM对象 if (MyObject != null) { System.Runtime.InteropServices.Marshal.ReleaseComObject(MyObject); MyObject = null; } // 赋值新的COM对象 MyObject = GetNewCOMObject(); } } public void Dispose() { lock (_lockObj) { if (MyObject != null) { System.Runtime.InteropServices.Marshal.ReleaseComObject(MyObject); MyObject = null; } // 告诉GC不需要再调用该对象的终结器 GC.SuppressFinalize(this); } } // 兜底逻辑:如果忘记手动调用Dispose,析构函数会释放资源 ~DisposeTest() { Dispose(); }
内容的提问来源于stack exchange,提问作者NMGod
相关产品推荐
相关产品推荐

