使用“ref”和/或“out”作为对象类型
题
我被 .Net 1.1 应用程序困住了(即我现在无法使用 2.0 中的泛型好东西),并且我正在尝试优化代码的某些部分。由于它处理大量需要释放的运行时可调用包装器,我最终创建了一个实用程序方法,该方法循环直到所有引用都被释放。该方法的签名是:
void ReleaseObject(object comObject)
释放所有 comObject 后,我调用 GC.Collect 和 GC.WaitForPendingFinalizers(不要问 - 任何处理 Office 互操作的人都知道)。
和 ...像往常一样,我遇到了一个极端情况 - 如果我在 GC.Collect 调用之前没有将相应的托管引用分配给 null,则它不会正确清理。
所以,我的代码如下所示:
ReleaseObject(myComObject);
myComObject = null;
GC.Collect()
...
由于有一堆 xxx=null,我决定将其放入 util 方法中,但由于通过引用传递和传递引用参数之间存在差异,显然我必须将方法更改为:
void ReleaseObject(out object comObject)
{
//do release
comObject = null;
}
并将调用者编辑为:
MyComClass myComObject = xxxx;
ReleaseObject(out myComObject);
失败并显示一条消息:“无法从‘out MyComClass’转换为‘out object’”
虽然我可以想到为什么这可能是一个问题(即从对象到 MyComClass 的反向转换不是隐式的,并且不能保证该方法会做什么),我想知道是否有解决方法,或者我需要保留数百个空值分配。
笔记:我有一堆不同的 COM 对象类型,这就是为什么我需要一个“对象”参数,而不是类型安全的参数。
解决方案
为什么调用方法比仅将变量设置为null更好?它们都是单线呼叫,而后者则更简单。
虽然你需要将它们设置为null,但听起来很奇怪。这些静态变量或实例变量的值是否需要在其包含对象之前发布?如果变量只是一个局部变量,无论如何都会超出范围,将其设置为null应该没有任何区别(在发布中)。
RCW没有实现IDisposable吗?如果是这样,调用Dispose(最好通过using语句)将是最好的选择。
(在评论中讨论后。)
这些是局部变量,稍后在方法中不会引用。这意味着垃圾收集器将意识到它们不需要被视为“root”。引用 - 所以将它们设置为null应该没有任何区别。
然而,直接回答原始问题:不,你不能通过引用传递变量,除非方法参数的类型完全相同,所以你在这里运气不好。 (使用泛型它是可能的,但你已经说过你只限于.NET 1.1。)
其他提示
Sunny、ref 和 out 是编译器的编组提示和契约。Ref 和 out 是 COM 时代的延续——通过线路/在进程之间发送对象时的编组提示。
这 out
合同
void foo( out MyClass x)
- foo() 将设置
x
在它返回之前的某事。 x
输入 foo() 时没有值,如果尝试使用,则会出现编译器错误x
在设置之前。(使用未分配的输出参数 x)
这 ref
合同
void foo( ref MyClass x)
- ref 允许更改调用者引用。
- x 必须是可分配的
- 你不能将某些东西转换为中间变量 foo( ref (object) Something)
- x 不能是属性
最后两点的现实可能会阻止您做您想做的事情,因为实际上,当您了解参考文献的真正含义时,它们就毫无意义了。如果你想知道这一点,请询问乔恩·斯基特(Jon Skeet)(他写了一本书)。
当编组 ref 时,它表示除了返回值之外,还返回 ref 值。当编组出时,它表示调用方法时不必费心发送出值,但请记住除了返回值之外还要带回出值。
免责声明 免责声明 免责声明
正如其他人指出的那样,有些可疑的事情正在发生。看来您正在维护的暴力代码存在一些微妙的错误,并且巧合地遭受了编码的困扰。最好的解决方案可能是添加另一层间接层。IE。包装器类的包装器,可确保确定性清理,您可以一次且仅编写一次混乱的代码,而不是将其散布在整个代码库中。
那是说..
替代方案1
除非您为调用它的每种类型的 (com) 对象提供重载,否则 Ref 不会成功。
// need a remove method for each type.
void Remove( ref Com1 x ) { ...; x = null; }
void Remove( ref Con2 x ) { ...; x = null; }
void Remove( ref Com3 x ) { ...; x = null; }
// a generics version using ref.
void RemoveComRef<ComT>(ref ComT t) where ComT : class
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(t);
t = null;
}
Com1 c1 = new Com1();
Com2 c2 = new Com2();
Remove( ref c1 );
RemoveComRef(ref c2); // the generics version again.
替代方案2
如果您不想这样做,请从 Remove() 方法返回 null 并强制转换回对象类型。
class Remover
{
// .net 1.1 must cast if assigning
public static object Remove(object x)
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(x);
return null;
}
// uses generics.
public static ComT RemoveCom<ComT>(ComT t) where ComT : class
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(t);
return null;
}
}
Com1 c1 = new Com1();
Com2 c2 = new Com2();
c1 = (Com1)Remover.Remove(c1); // no reliance on generics
c2 = Remover.RemoveCom(c2); // relies on generics
* 我添加了通用版本进行比较。
上面的代码的效果是,当您查看代码时,如果您看到对 Remove(x) 的调用但没有赋值(使错误的代码看起来是错误的),您就会产生怀疑。您甚至可以在代码库中使用 Grep 查找未发生赋值的对 Remove 的调用。
免责声明 - 以上所有内容都基于您需要手动将引用设置为 null,这(通常)是没有必要的。
在我看来,你将无法在另一种方法中将这些对象设置为null(顺便说一下,你需要使用 ref 参数而不是 out 来制作它工作,无论如何你会用“无法转换...”错误来解决同样的问题。) 我建议创建对象数组,然后迭代遍历该数组,调用ReleaseObject方法并将这些对象设置为null。类似的东西:
List garbagedObjects = new List();
garbagedObjects.Add(myComObject1);
garbagedObjects.Add(myComObject2);
...
foreach(object garbagedObject in garbagedObjects)
{
ReleaseObject(garbagedObject);
garbagedObject = null;
}
garbagedObjects = null;
GC.Collect();
...
您应该致电 Marshal.ReleaseComObject
,其中AFAIK在1.1中可用。
你可能的意思是“引用”:
static void ReleaseObject(ref object comObject)
{
if(comObject != null)
{
//do release
comObject = null;
}
}
[编辑注释]但是,这只适用于无类型的对象,所以没有泛型就没用多少!哦,对于C#2.0 ......
重新“参考”;如果变量是真正的变量(意思是:方法变量),那么它们很快就会超出范围并被收集。 “参考”只对释放字段有用。但说实话,将它们设置为空会更简单......
典型的COM模式是:
SomeType obj = new SomeType();
try {
obj.SomeMethod(); // etc
} finally {
Marshal.ReleaseComObject(obj);
}