代码之家  ›  专栏  ›  技术社区  ›  Sunny Milenov

对对象类型使用“ref”和/或“out”

  •  2
  • Sunny Milenov  · 技术社区  · 17 年前

    我一直在使用.NET 1.1应用程序(也就是说,我现在不能使用2.0中的泛型产品),我试图优化代码的某些部分。因为它处理了很多需要发布的运行时可调用包装器,所以我最终创建了一个实用方法,该方法循环直到所有引用都被发布。方法的签名是:

    void ReleaseObject(object comObject)
    

    在发布了所有的ComObject之后,我调用gc.collect和gc.waitForPendingFinalizer(不要问——任何处理Office Interop的人都知道)。

    还有…和往常一样,我遇到了一个棘手的问题——如果在gc.collect调用之前没有将相应的托管引用分配给空,那么它就不能正确地清除。

    所以,我的代码如下:

    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 mycommclass'转换为'out object'”

    虽然我可以思考为什么它可能是一个问题(即,从对象到MyComClass的反向转换不是隐式的,也不能保证该方法会做什么),但我想知道是否有解决方法,或者我需要保留我的数百个空值赋值。

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

    4 回复  |  直到 17 年前
        1
  •  2
  •   Jon Skeet    17 年前

    为什么调用方法比将变量设置为空要好?它们都是单线电话,后者要简单得多。

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

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

    (在评论中讨论之后。)

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

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

        2
  •  3
  •   Robert Paulson    17 年前

    sunny、ref和out是编译器的编组提示+契约。REF和OUT是对COM天的一种延续——当通过线/进程之间发送时,对象的编组提示。

    这个 out 合同

    void foo( out MyClass x)
    
    1. ()将设置 x 在它回来之前。
    2. X 输入foo()时没有值,如果尝试使用 X 在设置之前。(使用未分配的参数x)

    这个 ref 合同

    void foo( ref MyClass x)
    
    1. REF允许更改调用方引用。
    2. X必须是可分配的
      • 不能将某些内容强制转换为中间变量foo(ref(object)something)
      • X不能是属性

    最后两点的现实可能会阻止你做你想做的事情,因为实际上,当你理解什么是真正的参考时,它们是没有意义的。如果你想知道,可以问乔恩·斯基特(他写了一本书)。

    在整理引用时,它表示除了返回值之外,还将返回引用值。 在整理输出时,它说在调用方法时不需要发送输出值,但记住除了返回值之外还要返回输出值。


    免责声明免责声明

    正如其他人指出的,一些可疑的事情正在发生。您维护的蛮力代码似乎有一些细微的错误,并且由于巧合而遭受了编码的痛苦。最好的解决方案可能是添加另一层间接寻址。也就是说,包装器类的包装器,它确保了确定性清理,在这种清理中,您可以一次又一次地编写混乱的代码,而不是将其贯穿整个代码库。


    说…

    替代方案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()方法返回空值,并将其转换回对象类型。

    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
    

    *我添加了通用版本进行比较。

    上述代码的作用是,当查看代码时,当您看到一个调用在不进行赋值的情况下删除(x)时,您会变得可疑(使错误的代码看起来是错误的)。你甚至可以通过代码库寻找调用来删除不发生分配的地方。


    免责声明-以上所有内容都是基于您需要手动将引用设置为空,这(通常)是不必要的。

        3
  •  0
  •   Leopold Cimrman    17 年前

    在我看来,在另一个方法中,您将无法将这些对象设置为空(btw您需要使用 裁判 参数而不是 外面的 要使其正常工作,无论如何,您会遇到相同的问题,即“无法转换…”错误。) 我建议创建和数组对象,而不是遍历该数组,调用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
  •  0
  •   Marc Gravell    17 年前

    你应该打电话来 Marshal.ReleaseComObject ,Afaik在1.1中提供。

    你可能是说“参考号”:

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

    [edit re comments]但是,这只适用于非类型化的对象,因此没有泛型就不能使用太多!哦,对于C 2.0…

    是“ref”;如果变量是真正的变量(意思是:方法变量),那么它们很快就会超出范围并被收集。“ref”只对释放字段有用。但说实话,把它们设为空会更简单…

    典型的COM模式是:

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