Liberando objetos COM temporales

Considere el siguiente código C # utilizando un objeto COM.

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

Esto liberará los objetos COM o baz , pero no los objetos temporales que regresan por o.Foo y o.Foo.Bar . Esto puede causar problemas cuando esos objetos contienen una gran cantidad de memoria no administrada u otros recursos.

Una solución obvia pero fea sería desordenar el código aún más con try-finally y Marshal.ReleaseComObject . Ver C # + COM Interop, lanzamiento determinístico

Como solución, creé una clase de ayuda

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

Uso:

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 } 

Mis preguntas: ¿Hay problemas potenciales con este código? ¿Alguien tiene una solución más elegante?

Mi mayor queja sería el nombre, T ; Add podría ser más ilusorio del uso. También agregaría where T : class al método genérico, pero la “API fluida” parece utilizable. También me inclinaría a aplanar el código un poco. También puedo ver algunas formas de usar la API Expression para recorrer un árbol completo y capturar todos los pasos intermedios, pero no sería trivial , pero imagine:

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

donde eso es un árbol de expresión y obtenemos los intermediarios automáticamente.

(también, puede Clear() o null la lista en Dispose() )


Al igual que:

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

La solución de Marc Gravell no funcionará con .Net 4. + debido a la introducción de Dynamic en COM en lugar de objeto. Además, cuando se prueba con el COM de Excel, hay una excepción con el constructor que dice “Convertir no compatible” (predeterminado del interruptor de WalkImpl).

Existen otras limitaciones con expresiones como no tener propiedades indexadas y sin argumentos opcionales. Nunca antes codifiqué Expresión. No tengo idea de cómo abordar estos problemas.

No comstackrá ni ejecutará:

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