Освобождение временных com-объектов
рассмотрим следующий код C# с помощью 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);
}
это освободит COM-объекты o
и baz
, но не временные объекты возвращаются o.Foo
и o.Foo.Bar
.
Это может вызвать проблемы, когда эти объекты имеют большой объем неуправляемой памяти или других ресурсов.
очевидное, но уродливое решение было бы, чтобы загромождать код еще больше с try-finally
и Marshal.ReleaseComObject
. Видеть
взаимодействие c# + COM, детерминированное отпустите
в качестве обходного пути, я создал вспомогательный класс
class TemporaryComObjects: IDisposable
{
public C T<C>(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 = t.T(new MyComObject);
var baz = t.T(t.T(t.T(o.Foo).Bar).Baz);
// do something with baz
}
мои вопросы: Есть ли потенциальные проблемы с этим кодом? Есть ли у кого-нибудь более элегантное решение?
2 ответов
моей самой большой проблемой было бы имя,T
; Add
может быть более illusrative использования. Я бы также добавил where T : class
к общему методу, но "fluent API" кажется полезным. Я также склонен немного сгладить код. Я также вижу некоторые способы использования Expression
API, чтобы пройти все дерево и захватить все промежуточные шаги, но это не будет тривиальные - но представь себе:
using(var com = new SomeWrapper()) {
var baz = com.Add(() => new MyComObject().Foo.Bar.Baz);
}
где это дерево выражений, и мы получаем посредников автоматически.
(кроме того, вы можете Clear()
или null
список Dispose()
)
вот так:
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<object> objects = new List<object>();
public T Add<T>(Expression<Func<T>> 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<Expression> 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();
}
}
решение Марка Гравелла не будет работать с .Net 4.+ из-за введения Dynamic в COM вместо object. Кроме того, при тестировании с Excel COM существует исключение с конструктором, говорящим "конвертировать не поддерживается" (по умолчанию переключатель WalkImpl).
существуют другие ограничения с выражениями как без индексированных свойств и без дополнительных аргументов. Никогда не закодированное выражение до этого, я понятия не имею, как обратиться к этим проблемы.
не будет компилироваться или выполняться:
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;
}