释放临时COM对象

使用COM对象考虑以下C#代码。

MyComObject o = new MyComObject; try { var baz = o.Foo.Bar.Baz; try { // do something with baz } finally { Marshal.ReleaseComObject(baz); } } finally { Marshal.ReleaseComObject(o); } 

这将释放COM对象obaz ,但不释放由o.Fooo.Foo.Bar的临时对象。 当这些对象拥有大量非托管内存或其他资源时,这可能会导致问题。

一个明显但丑陋的解决方案是,使用try-finallyMarshal.ReleaseComObject使代码更加混乱。 请参阅C#+ COM Interop,确定性版本

作为一种解决方法,我创建了一个帮助类

class TemporaryComObjects: IDisposable { public C T(C comObject) { m_objects.Add(comObject); return comObject; } public void Dispose() { foreach (object o in m_objects) Marshal.ReleaseComObject(o); } }
class TemporaryComObjects: IDisposable { public C T(C comObject) { m_objects.Add(comObject); return comObject; } public void Dispose() { foreach (object o in m_objects) Marshal.ReleaseComObject(o); } } 

用法:

using (TemporaryComObjects t = new TemporaryComObjects()) { MyComObject o = tT(new MyComObject); var baz = tT(tT(tT(o.Foo).Bar).Baz); // do something with baz }
using (TemporaryComObjects t = new TemporaryComObjects()) { MyComObject o = tT(new MyComObject); var baz = tT(tT(tT(o.Foo).Bar).Baz); // do something with baz } 

我的问题:此代码是否存在潜在问题? 有人有更优雅的解决方案吗?

我最大的抱怨是名字, T ; Add可能更具说明性。 我还将where T : class添加到generics方法中,但“流畅的API”似乎可用。 我也倾向于将代码扁平化。 我还可以看到一些使用Expression API来遍历整个树并捕获所有中间步骤的方法,但这不是一件容易的事 – 但想象一下:

 using(var com = new SomeWrapper()) { var baz = com.Add(() => new MyComObject().Foo.Bar.Baz); } 

这是一个表达式树,我们自动获得中介。

(另外,你可以在Dispose() Clear()Clear()列表)


像这样:

 static class ComExample { static void Main() { using (var wrapper = new ReleaseWrapper()) { var baz = wrapper.Add( () => new Foo().Bar.Baz); Console.WriteLine(baz.Name); } } } class ReleaseWrapper : IDisposable { List objects = new List(); public T Add(Expression> func) { return (T)Walk(func.Body); } object Walk(Expression expr) { object obj = WalkImpl(expr); if (obj != null && Marshal.IsComObject(obj) && !objects.Contains(obj)) { objects.Add(obj); } return obj; } object[] Walk(IEnumerable args) { if (args == null) return null; return args.Select(arg => Walk(arg)).ToArray(); } object WalkImpl(Expression expr) { switch (expr.NodeType) { case ExpressionType.Constant: return ((ConstantExpression)expr).Value; case ExpressionType.New: NewExpression ne = (NewExpression)expr; return ne.Constructor.Invoke(Walk(ne.Arguments)); case ExpressionType.MemberAccess: MemberExpression me = (MemberExpression)expr; object target = Walk(me.Expression); switch (me.Member.MemberType) { case MemberTypes.Field: return ((FieldInfo)me.Member).GetValue(target); case MemberTypes.Property: return ((PropertyInfo)me.Member).GetValue(target, null); default: throw new NotSupportedException(); } case ExpressionType.Call: MethodCallExpression mce = (MethodCallExpression)expr; return mce.Method.Invoke(Walk(mce.Object), Walk(mce.Arguments)); default: throw new NotSupportedException(); } } public void Dispose() { foreach(object obj in objects) { Marshal.ReleaseComObject(obj); Debug.WriteLine("Released: " + obj); } objects.Clear(); } } 

Marc Gravell的解决方案不适用于.Net 4. +,因为在COM而不是对象中引入了Dynamic。 此外,在使用Excel COM进行测试时,构造函数会出现“转换不支持”(WalkImpl开关的默认值)的exception。

Expressions还有其他限制,因为没有索引属性,也没有可选参数。 从来没有编码表达式之前我不知道如何解决这些问题。

不会编译或执行:

 using (var wrapper = new ComWrapper()) { var application = wrapper.Add(() => new Excel.Application()); var workbook = wrapper.Add(() => application.Workbooks.Open(@"C:\MyExcel.xls")); Excel.Range range = wrapper.Add(() => workbook.Sheets[1].UsedRange); string value = wrapper.Add(() => range.Cells[1, 1]).Value2; }