我被 .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)
  1. foo() 将设置 x 在它返回之前的某事。
  2. x 输入 foo() 时没有值,如果尝试使用,则会出现编译器错误 x 在设置之前。(使用未分配的输出参数 x)

ref 合同

void foo( ref MyClass x)
  1. ref 允许更改调用者引用。
  2. 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);
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top