C# 使用包装器对象正确清理excel互操作对象
所有这些问题:C# 使用包装器对象正确清理excel互操作对象,c#,excel,interop,com-interop,C#,Excel,Interop,Com Interop,所有这些问题: 解决C#在使用Excel COM对象后不能正确释放这些对象的问题。围绕这一问题的工作主要有两个方向: 当不再使用Excel时终止Excel进程 首先要注意将每个COM对象显式地分配给一个变量,并确保最终对每个COM对象执行Marshal.ReleaseComObject 有人说2太乏味了,而且在代码中的某些地方,您是否忘记遵守这条规则总是存在一些不确定性。对我来说,Still1看起来很脏而且容易出错,而且我猜在受限环境中,试图杀死进程可能会引发安全错误 所以我一直在考虑
- 每个Excel互操作类都有其代理,该代理封装该类的一个对象
- 代理在其终结器中释放COM对象
- 代理模仿互操作类的接口
- 任何最初返回COM对象的方法都将改为返回代理。其他方法只是将实现委托给内部COM对象
public class Application
{
private Microsoft.Office.Interop.Excel.Application innerApplication
= new Microsoft.Office.Interop.Excel.Application innerApplication();
~Application()
{
Marshal.ReleaseCOMObject(innerApplication);
innerApplication = null;
}
public Workbooks Workbooks
{
get { return new Workbooks(innerApplication.Workbooks); }
}
}
public class Workbooks
{
private Microsoft.Office.Interop.Excel.Workbooks innerWorkbooks;
Workbooks(Microsoft.Office.Interop.Excel.Workbooks innerWorkbooks)
{
this.innerWorkbooks = innerWorkbooks;
}
~Workbooks()
{
Marshal.ReleaseCOMObject(innerWorkbooks);
innerWorkbooks = null;
}
}
我特别向您提出的问题是:
- 谁觉得这是个坏主意?为什么
- 谁觉得这是个好主意?如果是这样,为什么还没有人实施/发布这样的模型?这仅仅是因为我的努力,还是我忽略了这个想法的致命问题
- 在终结器中执行ReleaseCOMObject是否不可能/错误/容易出错?(我只看到过将其放入Dispose()而不是终结器中的建议-为什么?)
- 如果这种方法有意义,有什么改进的建议吗
相反,如果您想尝试显式清理,则“不要对COM对象使用两点”指导原则将帮助您记住保留对您创建的每个对象的引用,以便在完成清理时可以清理它们。我们使用MSDN杂志中描述的LifetimeScope类。正确地使用它可以清理对象,并在Excel导出中发挥了巨大的作用。代码可在此处下载,还包含杂志文章: 我会做什么:
class ScopedCleanup<T> : IDisposable where T : class
{
readonly Action<T> cleanup;
public ScopedCleanup(T o, Action<T> cleanup)
{
this.Object = o;
this.cleanup = cleanup;
}
public T Object { get; private set; }
#region IDisposable Members
public void Dispose()
{
if (Object != null)
{
if(cleanup != null)
cleanup(Object);
Object = null;
GC.SuppressFinalize(this);
}
}
#endregion
~ScopedCleanup() { Dispose(); }
}
static ScopedCleanup<T> CleanupObject<T>(T o, Action<T> cleanup) where T : class
{
return new ScopedCleanup<T>(o, cleanup);
}
static ScopedCleanup<ComType> CleanupComObject<ComType>(ComType comObject, Action<ComType> actionBeforeRelease) where ComType : class
{
return
CleanupObject(
comObject,
o =>
{
if(actionBeforeRelease != null)
actionBeforeRelease(o);
Marshal.ReleaseComObject(o);
}
);
}
static ScopedCleanup<ComType> CleanupComObject<ComType>(ComType comObject) where ComType : class
{
return CleanupComObject(comObject, null);
}
看看我的项目。通过本机VB.NET后期绑定功能解决了Referencech包装器对象和本机对象的问题。值得一提的是,使用以下逻辑:
public static void UsingCOM<T>(T reference, Action<T> doThis) where T : class
{
if (reference == null) return;
try
{
doThis(reference);
}
finally
{
Marshal.ReleaseComObject(reference);
}
}
publicstaticvoid使用com(T引用,actiondothis),其中T:class
{
if(reference==null)返回;
尝试
{
doThis(参考);
}
最后
{
Marshal.ReleaseComObject(参考);
}
}
您基本上对终结器的看法是正确的,但在这种特殊情况下,我可能会说:让我们将责任委托给垃圾收集器,因为由于终结器的存在,我可以依靠GC在内部对象被销毁后也会释放COM对象。如果我想在代码的某个地方明确地释放所有资源,我可以使用GC.Collect()。@chiccodoro:这个方法的问题是它依赖于GC的实现细节。如果GC稍有变化,它可能不再工作。但是我理解在这种情况下正确操作的困难,而且在某些情况下,黑客可能是最具成本效益的解决方案。我明白你的意思。但是对每个点使用using()是很乏味的。可能只在运行GC.Collect()的Excel.Application上定义一个Dispose()方法,以便此时也释放所有其他Excel对象?在Excel.Application离开“使用”范围之前,我不要求我的程序释放源于该应用程序的COM资源。@chiccodoro:您可以创建一个包装类,该类允许您访问Excel.Application对象,并在该类的Dispose中执行GC.Collect trick.Hi Rotaerk。这看起来像是IDisposable方法的一个有趣的增强,但它并没有回答我的问题。(或者我没有抓住要点。)正如前面提到的,使用IDisposable是一种方法,而不是终结器。终结器将确保资源已清理,但无法控制何时清理。Collect不能保证立即收集所有内容,因此它不是解决方案。一次性塑料袋可以让你严格控制何时释放。此外,using语句允许您以异常安全的方式执行此操作。(请记住,调试时不要在清理前过早停止应用程序,否则将无法完成,并且
public static void UsingCOM<T>(T reference, Action<T> doThis) where T : class
{
if (reference == null) return;
try
{
doThis(reference);
}
finally
{
Marshal.ReleaseComObject(reference);
}
}