来自Excel VBA背景我会经常编写如下代码:
Range("myRange").Offset(0, 1).Resize(1, ccData).EntireColumn.Delete
我现在正在转向VSTO,并且一直在阅读RCW计数器等,以及明确释放COM对象的需要。基本建议似乎是:不要将Excel对象的引用链接在一起(如上所述) - 因此"一个点好,两个点坏"。 我的问题是,我是否正确上述代码不是VSTO的方式?如果是这样,这是否意味着我需要明确声明上述链中隐含的3个范围(Offset,Resize& EntireColumn)?
甚至如此:
rng.Columns.Count
其中rng是声明的范围?我应该为rng.Columns指定一个名称,以获得范围内的列数吗?
答案 0 :(得分:6)
“双点规则”愚蠢背后存在着非常不利的货物崇拜,它完全无法让C#程序员从第4版开始就摆脱麻烦。而且,simple way使Office程序退出的过程更加痛苦。需求。
但这不是根本不存在的问题,货物崇拜仅适用于使用自动化激活Office程序的进程外程序。你的代码实际上是在里面运行Office程序,你当然不关心程序什么时候终止。因为这也会终止你的代码。
只需按照编写常规C#代码的方式编写代码,GC不需要任何帮助。
答案 1 :(得分:0)
我是否认为上述代码不适合VSTO?
是的,你在正确的大道上。您需要声明不同的对象以便稍后释放它们。完成使用后,使用System.Runtime.InteropServices.Marshal.ReleaseComObject释放Office对象。然后在Visual Basic中将变量设置为Nothing(C#中为null)以释放对该对象的引用。
您可以在Systematically Releasing Objects文章中详细了解相关内容。它与Outlook有关,但相同的原则可以应用于所有Office应用程序。
BTW:它不依赖于VSTO。它来自COM世界......答案 2 :(得分:0)
正如小肯定经常发生的那样,双点规则需要进一步解释。您可以将其用作助记符。
原因是你在.NET中获得的每个新RCW都将保存对相应COM对象的引用。所以,如果你有(假设obj
是RCW,这是你第一次获得其他对象):
obj.Property[0].MethodThatReturnsAnotherObject()
// 1 2 3
你还有3个RCW。如您所见,只有一个额外的点。虽然属性可能是获取其他COM对象的最常用方法,但它并不是唯一的方法。
通常,每个RCW只会在收集垃圾时释放底层COM对象,除非您使用Marshal.ReleaseComObject
。只有在完全确定时才使用此方法,您是唯一使用您正在发布的RCW的人。
要完全清楚这个主题:
ReleaseComObject
或FinalReleaseComObject
,这显然必须完全确定你的代码是唯一提到RCW的代码<type> propObj;
try
{
propObj = obj.Property;
<type> propArrayObj;
try
{
propArrayObj = propObj[0];
<type> propArrayObjReturn;
try
{
propArrayObjReturn = propArrayObj.MethodThatReturnsAnotherObject();
}
finally
{
if (propArrayObjReturn != null) Marshal.ReleaseComObject(propArrayObjReturn);
}
}
finally
{
if (propArrayObj != null) Marshal.ReleaseComObject(propArrayObj);
}
}
finally
{
if (propObj != null) Marshal.ReleaseComObject(propObj);
}
这很乏味,包装器可能会有所帮助:
using System;
using System.Runtime.InteropServices;
using System.Threading;
public class ComPtr<T> : IDisposable where T : class
{
public ComPtr(T comObj)
{
if (comObj == null) throw new ArgumentNullException("comObj");
if (!typeof(T).IsInterface)
{
throw new ArgumentException("COM type must be an interface.", "T");
}
// TODO: check interface attributes: ComImport or ComVisible, and Guid
this.comObj = comObj;
}
private T comObj;
public T ComObj
{
get
{
// It's not best practice to throw exceptions in getters
// But the alternative might lead to a latent NullReferenceException
if (comObj == null)
{
throw new ObjectDisposedException("ComObj");
}
return comObj;
}
}
~ComPtr()
{
Dispose(false);
}
// IDisposable
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
#if !RELEASECOMPTR
// Option 1: Safe. It might force the GC too often.
// You can probably use a global limiter, e.g. don't force GC
// for less than 5 seconds apart.
if (Interlocked.Exchange(ref comObj, null) != null)
{
// Note: GC all generations
GC.Collect();
// WARNING: Wait for ALL pending finalizers
// COM objects in other STA threads will require those threads
// to process messages in a timely manner.
// However, this is the only way to be sure GCed RCWs
// actually invoked the COM object's Release.
GC.WaitForPendingFinalizers();
}
#else
// Option 2: Dangerous! You must be sure you have no other
// reference to the RCW (Runtime Callable Wrapper).
T currentComObj = Interlocked.Exchange(ref comObj, null);
if (currentComObj != null)
{
// Note: This might (and usually does) invalidate the RCW
Marshal.ReleaseComObject(currentComObj);
// WARNING: This WILL invalidate the RCW, no matter how many
// times the object reentered the managed world.
// However, this is the only way to be sure the RCW's
// COM object is not referenced by our .NET instance.
//Marshal.FinalReleaseComObject(currentComObj);
}
#endif
}
}
这会让上一个例子更友好一点:
using (var prop = new ComObj<type>(obj.Property))
{
using (var propArray = new ComObj<type>(prop.ComObj[0]))
{
using (var propArrayReturn = new ComPtr<type>(propArray.ComObj.MethodThatReturnsAnotherObject()))
{
}
}
}
要避免ComObj
属性,您可以实现代理,但我会将其作为练习。具体而言,进行有效的代理生成,而不是通过反射进行转发。