【问题标题】:How does the keyword `ref` affect memory management and garbage collection?关键字`ref`如何影响内存管理和垃圾收集?
【发布时间】:2016-04-10 02:10:45
【问题描述】:

我是 C# 的新手,我一直在搞乱“ref”、“out”和指针,我对“ref”的工作原理有一个普遍的疑问,尤其是在使用对象而不是原始类型时。说这是我的方法:

public void foo(ref Point p) {
     p.set(1,1);   // the x/y values are updated without constructing a new Point
}

和类似的方法:

public void bar(Point p) {
     p.set(1,1);   // the x/y values are updated without constructing a new Point
}

EDITPoint 在这两种情况下都是一个类

两者都有效,但其中一种是否比另一种更具成本效益?我知道在 C++ 中,如果你传入一个指针,你只会给出内存地址;根据我对 C# 的理解,由于自动垃圾收集,您不能将 Object* 传递给方法。 'ref' 是否将对象固定到某个位置?此外,如果您将对象传递给方法,例如上面的“bar”,它是传递对象的副本还是传递指针/引用?

澄清:在我的书中,它确实说如果你想要一个更新原语的方法,例如int,你需要使用ref(如果是out未初始化)或*。我在问对象是否也是如此,如果将对象作为参数而不是 ref 传递给对象成本更高。

【问题讨论】:

  • 您的示例中Point 的类型是什么?如果Point 是一个类类型,那么在对foo 的调用完成后,对p 的更改将可见,但如果Pointstruct,那么如果没有ref,更改将不会在上游调用者看到的方式。
  • Point 是一个类。在这种情况下是否像 Java 一样,Object 捆绑了一个指针?
  • 你没有传递一个对象。在这两种情况下,您都传递了对对象的引用。
  • 所以ref* 都只对改变原始类型有用? (当它们是方法的参数时)

标签: c# memory-management garbage-collection ref


【解决方案1】:
  • 如果您的类型是结构,ref 大致相当于指向该结构的指针。此处未创建新实例。不同之处(与在没有ref 的情况下传递它)是您现在可以改变包含在该变量中的原始结构实例。
  • 如果你的类型是一个类,ref 只是增加了一个间接级别。这里也没有创建新的实例。不同之处(与在没有ref 的情况下传递它)是您现在可以用其他东西完全替换(而不仅仅是变异)该变量引用的原始类实例。

由于在这两种情况下都没有创建新实例,垃圾收集器可能不会以任何重要的方式关心这一点。

【讨论】:

  • 我将您的更新为 正确 答案,因为它最清楚地回答了原始问题。
【解决方案2】:

其实class是一个引用类型,就是引用类型的变量持有对其数据的引用,而不是像值类型一样直接持有数据。

当您将引用类型的变量作为方法参数传递时,它传递的是对该数据的引用,而不是数据本身。因此,如果更新对象的某些属性,则更新会反映在原始变量中,除非您重新分配参数。

来自 MSDN 的示例:

class PassingRefByVal 
{
    static void Change(int[] pArray)
    {
        pArray[0] = 888;  // This change affects the original element.
        pArray = new int[5] {-3, -1, -2, -3, -4};   // This change is local.
        System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
    }

    static void Main() 
    {
        int[] arr = {1, 4, 5};
        System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr [0]);

        Change(arr);
        System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr [0]);
    }
}
/* Output:
    Inside Main, before calling the method, the first element is: 1
    Inside the method, the first element is: -3
    Inside Main, after calling the method, the first element is: 888
*/

使用 ref 关键字传递引用类型的变量将反映对原始变量的任何更改,即使您重新输入参数也是如此。

来自 MSDN 的示例:

class PassingRefByRef 
{
    static void Change(ref int[] pArray)
    {
        // Both of the following changes will affect the original variables:
        pArray[0] = 888;
        pArray = new int[5] {-3, -1, -2, -3, -4};
        System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
    }

    static void Main() 
    {
        int[] arr = {1, 4, 5};
        System.Console.WriteLine("Inside Main, before calling the method, the first element is: {0}", arr[0]);

        Change(ref arr);
        System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);
    }
}
/* Output:
    Inside Main, before calling the method, the first element is: 1
    Inside the method, the first element is: -3
    Inside Main, after calling the method, the first element is: -3
*/

MSDN 文档。

【讨论】:

  • 这是不正确的。除非您使用refout,否则参数按值传递。请看msdn.microsoft.com/en-us/library/0f66670z.aspx
  • 引用类型总是通过引用传递,除了不能更新引用本身,请看msdn.microsoft.com/en-us/library/s6938f28.aspx
  • 引入自己的术语,使用与标准术语相同的术语,但赋予它们不同的定义,不一定是无效的,但当你在这里使用你的定义回答问题时,肯定会造成很多不必要的混乱.您所说的“通过引用”显然不是 MSDN 所说的“通过引用”。您引用了一篇包含“当您通过值传递引用类型参数时”的 MSDN 文章来支持您声称“引用类型始终通过引用传递”这一事实是一个巨大的危险信号。
  • 你显然是在玩弄我的话,但没关系,我会更新我的答案以消除混乱。
  • 我不是在玩,因为我评论的原因确实让我感到困惑。现在看起来好多了,快速重读后我没有发现任何明显的问题。我不认为它回答了 OP 提出的字面问题,但鉴于 OP 最终接受的答案也没有这样做,那一定不是 OP 所追求的。
【解决方案3】:

类与结构的快速区分:

类是引用类型。当创建类的对象时, 分配给对象的变量仅包含对 那个记忆。当对象引用被分配给一个新变量时, 新变量引用原始对象。通过所做的更改 一个变量反映在另一个变量中,因为它们都 参考相同的数据。

结构是一种值类型。当一个结构是 创建后,分配给结构的变量保存 struct 的实际数据。当结构被分配给一个新变量时, 它被复制了。因此新变量和原始变量 包含相同数据的两个单独副本。对一份副本所做的更改 不影响其他副本。

https://msdn.microsoft.com/en-us/library/ms173109.aspx

您的示例很棘手,因为在 c# 中 Point 是一个不可变结构,而不是一个对象。

希望这个例子能帮助展示结构和对象在有和没有 ref 的情况下会发生什么。

public static void StructTest()
{
    var fooStruct = new MyStruct();
    var barStruct = new MyStruct();

    Console.WriteLine(fooStruct.Value); // prints 0
    Console.WriteLine(barStruct.Value); // prints 0

    fooStruct(ref fooStruct);
    barStruct(barStruct);

    // Struct value only changes when passed by reference.
    Console.WriteLine(fooStruct.Value); // prints 1
    Console.WriteLine(barStruct.Value); // prints 0    
}

public void fooStruct(ref MyStruct m) 
{
    m.Value++;
}

public void barStruct(MyStruct m)
{
    m.Value++;
}

public static void ObjectTest()
{
    var fooObject = new MyObject();
    var barObject = new MyObject();

    Console.WriteLine(fooObject.Value); // prints 0
    Console.WriteLine(barObject.Value); // prints 0

    fooObject(ref fooObject);
    barObject(barObject);

    // Objects are automatically passed by reference. No difference.
    Console.WriteLine(fooObject.Value); // prints 1
    Console.WriteLine(barObject.Value); // prints 1

    fooSetObjectToNull(ref fooObject);
    barSetObjectToNull(barObject);

    // Reference is actually a pointer to the variable that holds a reference to the object.
    Console.WriteLine(fooObject == null); // prints true
    Console.WriteLine(barObject == null); // prints false
}   

public void fooObject(ref MyObject m)
{
    m.Value++;
}

public void barObject(ref MyObject m)
{
    m.Value++;
}

public void fooSetObjectToNull(ref MyObject m)
{
    m = null;
}

public void barSetObjectToNull(MyObject m)
{
    m = null;
}

【讨论】:

  • 谢谢,我现在明白了。如果我想更新一个类中的变量,则不需要ref,但如果我想重新初始化它,则需要ref
猜你喜欢
  • 2012-11-30
  • 2011-02-26
  • 1970-01-01
  • 1970-01-01
  • 2012-04-30
  • 2013-04-25
  • 1970-01-01
  • 2016-07-31
  • 2016-03-11
相关资源
最近更新 更多