Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/286.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用包装器对象正确清理excel互操作对象_C#_Excel_Interop_Com Interop - Fatal编程技术网

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看起来很脏而且容易出错,而且我猜在受限环境中,试图杀死进程可能会引发安全错误 所以我一直在考虑

所有这些问题:

解决C#在使用Excel COM对象后不能正确释放这些对象的问题。围绕这一问题的工作主要有两个方向:

  • 当不再使用Excel时终止Excel进程
  • 首先要注意将每个COM对象显式地分配给一个变量,并确保最终对每个COM对象执行Marshal.ReleaseComObject
  • 有人说2太乏味了,而且在代码中的某些地方,您是否忘记遵守这条规则总是存在一些不确定性。对我来说,Still1看起来很脏而且容易出错,而且我猜在受限环境中,试图杀死进程可能会引发安全错误

    所以我一直在考虑通过创建另一个模仿Excel对象模型的代理对象模型来解决第2个问题(对我来说,它足以实现我实际需要的对象)。原则如下:

    • 每个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()而不是终结器中的建议-为什么?)
    • 如果这种方法有意义,有什么改进的建议吗
    在析构函数中执行ReleaseCOMObject是否不可能/不好/危险?(我只见过把它放在Dispose()而不是析构函数中的建议——为什么?)

    建议不要将清理代码放入终结器中,因为C++中的析构函数不同于它,它不是确定性的。它可能在对象超出范围后不久调用。可能需要一个小时。它可能永远不会被称为。通常,如果要处置非托管对象,应使用IDisposable模式,而不是终结器

    这是您通过显式调用垃圾收集器并等待终结器完成来尝试解决该问题的链接。一般来说,这是不推荐的,但是对于这种特殊情况,有些人认为这是一个可接受的解决方案,因为难以跟踪创建的所有临时非托管对象。但明确地说,清理是正确的做法。然而,考虑到这样做的困难,这种“黑客”可能是可以接受的。请注意,此解决方案可能比您提出的想法更好


    相反,如果您想尝试显式清理,则“不要对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);
            }
        }