【问题标题】:Using "ref" and/or "out" for Object type对对象类型使用“ref”和/或“out”
【发布时间】:2010-09-19 16:17:00
【问题描述】:

我坚持使用 .Net 1.1 应用程序(即我现在不能使用 2.0 中的泛型好东西),并且我正在尝试优化代码的某些部分。由于它处理了很多需要释放的运行时可调用包装器,因此我最终创建了一个实用方法,该方法循环直到所有引用都被释放。该方法的签名是:

void ReleaseObject(object comObject)

释放所有 comObjects 后,我调用 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'”

虽然我能想到为什么会出现问题(即从 object 到 MyComClass 的反向转换不是隐式的,并且无法保证该方法会做什么),但我想知道是否有解决方法,或者我需要保留我的数百个空值分配。

注意:我有一堆不同的 COM 对象类型,这就是为什么我需要一个“对象”参数,而不是一个类型安全的参数。

【问题讨论】:

    标签: c# .net casting ref out


    【解决方案1】:

    为什么调用方法比将变量设置为 null 更好?都是单行调用,后者简单很多。

    听起来很奇怪,您首先需要将它们设置为 null。这些静态变量,还是实例变量,其值需要在其包含对象之前释放?如果变量只是一个无论如何都会超出范围的局部变量,那么将其设置为 null 应该没有任何区别(在发布中)。

    RCW 不实现 IDisposable 吗?如果是这样,调用 Dispose(最好通过 using 语句)将是最好的选择。

    (在 cmets 讨论后。)

    这些是局部变量,稍后在方法中不会引用。这意味着垃圾收集器将意识到它们不需要被视为“根”引用 - 因此将它们设置为 null 应该没有任何区别。

    但是,要直接回答原始问题:不,您不能通过引用传递变量,除非方法参数的类型完全相同,因此您在这里不走运。 (使用泛型是可能的,但您说过您仅限于 .NET 1.1。)

    【讨论】:

    • @Jon: =null 是重复操作——这个重构:); RCW 不是 IDisposable;这是 Office 互操作特定的,我需要在范围结束之前强制 GC。正如我所说,我可以保持原样,但我很好奇是否有办法使用“out”来做到这一点。
    • 您将“反复将变量设置为空”重构为“反复调用方法”——好处在哪里?至于您是否需要这样做 - 将变量设置为 null 不会强制进行 GC。会发生什么(在发布模式下,而不是在调试器下,重要的是)...(续)
    • 如果您调用 GC.Collect() 而不将内容设置为 null?如果它们是局部变量,应该没问题。
    • 无论如何我都在调用实用程序方法 - 封装 ReleaseComObject,因此不会增加负载。不幸的是, GC.Collect 是不够的(都是本地变量)。我仅在发布版本中遇到问题,并且仅间接检测到它(继续)
    • 即因为客户端在以另一种方法调用 GC 之前会遇到一些困难。将 var 设置为 null 解决了这个问题。我只是好奇这是否可以用 ref 和/或 out 来完成,而不是必须这样做。只是从语言 (c#) 的角度来看很有趣。
    【解决方案2】:

    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 值。 在编组时,它说在调用方法时不要费心发送输出值,但记住除了返回值之外还要带回输出值。


    免责声明免责声明

    正如其他人指出的那样,正在发生一些可疑的事情。您维护的蛮力代码似乎有一些微妙的错误,并且由于巧合而受到编码的影响。最好的解决方案可能是添加另一层间接。即包装器类的包装器,可确保确定性清理,您可以在其中编写一次且仅一次的凌乱代码,而不是在整个代码库中散布它。


    这就是说..

    备选方案 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,这(通常)不是必需的。

    【讨论】:

    • 现在我必须做出艰难的决定。您在回答中付出了很多努力,并且很好地解释了 ref 的工作原理,但是您将 Jon 称为来源,并且他已经提供了答案。所以 - 给你点答案,但我会将乔恩的标记为已接受。谢谢。
    • 好吧,我说如果您想了解更多关于一般参考的信息,请询问 Jon,而不是我在扩展他的答案。反正我不太在乎rep。很高兴这个答案很有帮助。
    【解决方案3】:

    在我看来,您将无法在另一种方法中将这些对象设置为 null(顺便说一句,您需要使用 ref 参数而不是 out 来实现工作,无论如何你会遇到“无法转换......”错误的同样问题。) 我建议创建对象数组,然后遍历该数组,调用 ReleaseObject 方法并将这些对象设置为空。比如:

    List garbagedObjects = new List();
    garbagedObjects.Add(myComObject1);
    garbagedObjects.Add(myComObject2);
    ...
    foreach(object garbagedObject in garbagedObjects)
    {
      ReleaseObject(garbagedObject);
      garbagedObject = null;
    }
    garbagedObjects = null;
    GC.Collect();
    ...
    

    【讨论】:

    • 谢谢,但这增加了复杂性 - 即,我必须使用索引而不是按名称引用我的对象,这更容易出错。
    【解决方案4】:

    您应该致电 Marshal.ReleaseComObject,AFAIK 在 1.1 中可用。

    你的意思可能是“参考”:

    static void ReleaseObject(ref object comObject)
    {
       if(comObject != null)
       {
         //do release
         comObject = null;
       }
    }
    

    [edit re cmets] 但是,这仅适用于无类型对象,因此没有泛型就没有多大用处!哦,对于 C# 2.0...

    重新“参考”;如果变量是真正的变量(意思是:方法变量),那么它们很快就会超出范围并被收集。 “ref”仅对释放字段有用。但老实说,将它们设置为 null 会更简单...

    典型的 COM 模式是:

    SomeType obj = new SomeType();
    try {
      obj.SomeMethod(); // etc
    } finally {
      Marshal.ReleaseComObject(obj);
    }
    

    【讨论】:

    • 是的,我在实用程序方法的循环中使用 ReleaseComObject。这只是对 ref 和 out 如何工作的好奇,而不是具体情况,因为我已经有了解决方案:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-06
    • 1970-01-01
    • 2022-06-16
    • 2015-01-06
    • 2020-09-23
    相关资源
    最近更新 更多