C#中的ref修饰符用于...引用类型

4

我对这段代码有点困惑。

    public static void Foo(A p)
    {
        p.SomeProp = "ccc";
        p = null; // !!!
    }

    static void Main(string[] args)
    {
        A p = new A();
        Foo(p);

        Console.WriteLine("SomeProp is: " + p.SomeProp);
        Console.ReadLine();
    }

输出结果为:

"SomeProp is: ccc"

但我本来期待的是一个NullReferenceException异常。


然而,如果我像这样改变它,使用ref修饰符:

    public static void Foo(ref A p)
    {
        p.SomeProp = "ccc";
        p = null;
    }

    static void Main(string[] args)
    {
        A p = new A();
        Foo(ref p);

        Console.WriteLine("SomeProp is: " + p.SomeProp);
        Console.ReadLine();
    }

我遇到了一个NullReferenceException - 第二个错误对我来说是可以理解的。

但是第一段代码中,p并没有被设置为null,但是该属性却被赋值了,这是怎么回事呢?

我的问题是:如果第一段代码中的p参数不是对原始实例的引用,那它是什么?


顺便提一下,以下是类A的定义:

public class A
{
    public string SomeProp;
}

参见:https://dev59.com/SXVC5IYBdhLWcg3wykQt - Scott Langham
9个回答

17
在 .Net 中,所有东西都是按值传递的,除非你明确使用 refout 关键字。对于引用类型而言,这意味着传递的是引用的副本。
在第一个例子中,这意味着您的变量 p 仍然是指向同一对象的引用,因此设置属性的操作按照您预期的方式进行。但是当您将引用本身设置为 null 时,您改变的只是副本。

6
p.SomeValue = "ccc";

正在说:

  • 获取p所引用的对象
  • 将该对象上的SomeValue属性的值设置为"ccc"

    p = null;

正在说:

  • 将p更改为不再引用它以前引用的对象,而是现在引用null。

它并没有说要将p引用的对象更改为null,而是说局部变量p现在应该引用null。

默认情况下,当您传递类型A的参数(例如方法调用“Foo(p)”中的参数)时,您并没有传递由p引用的对象,甚至不是引用p,而是引用p所引用的对象。它们引用同一个对象,但它们不是相同的引用。即“public static void Foo(A p)”中的引用p与“Foo(p)”中的p不是同一个引用,但它们确实引用同一个对象。

您可以通过使用ref参数来更改此行为。这将使它们成为相同的引用,并且更改一个的值会更改另一个的值。


希望我现在明白了,感谢你和其他人的解释。 - Max

4

对象引用是按值传递的。 <-- 非常重要。

您的方法拥有该引用的一个副本,因此当您说p = null时,您只是改变了该引用的副本。当您返回时,原始引用“p”仍具有其原始值。

在第二个例子中,您明确按引用传递您的对象。因此,新的引用(null)被传回到您的调用函数(然后给出了null异常)。


3

p参数是对新创建的Foo实例引用的副本。可以将其视为指示牌:调用“new A()”在堆上创建一个A对象,并将其指示牌返回给您,您将其存储在p中。然后调用Foo函数,并向其提供指示牌的副本-它知道如何到达A对象并更新属性。然后它在指示牌上涂鸦-它无法再到达那里,但对象仍然存在。调用者仍有一个有效的指示牌,因此不会抛出异常。

使用“ref”参数的第二个实例实际上是说:“不要给我指示牌的副本,给我实际的指示牌”。这次,在它上面涂鸦时,传递给函数的原始指示牌也会丢失,并且会发生异常。

更技术性地说,C#在没有ref关键字的情况下始终是“按值调用”-所有参数都传递了参数值的副本。参数值是指示牌(或“引用”)的事实并不重要。


2

1
在第一个函数中,你只是将本地引用p设置为null。你没有将主函数中的p设置为null。在第二个函数中,通过使用ref,你确实将主函数中的p设置为null。

1

C# 除非使用 out/ref,否则默认采用传值方式。当你通过值传递引用时,引用会被复制。然而,由于它仍然指向堆上的同一对象,因此可以通过引用修改对象的状态。如果使用 ref,则传递引用的地址。将该引用设置为 null 将使实际引用为空,因此在此之后访问原始引用时会出现 NullReferenceException


0

如果您在调试时将“p”添加到监视列表并为其生成对象 ID,那么它的 ID 与函数 Foo(p) 中的“p”的 ID 相同。因此,在调用方法中的“p”和 Foo 中的“p”具有相同的对象 ID。


-1
当你通过值传递对象引用时,你可以改变它的状态。但是当你通过引用传递对象引用时,你不仅可以改变它的状态,还可以改变实际的对象本身。
编辑:在知道对象永远不会被传递为值或引用之后,我们知道传递的是一个对象的引用作为值或引用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接