c# - 引用类型的ref修饰符

时间:2009-09-21 15:41:04

标签: c#

我对这段代码感到有点困惑。

    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是: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,但属性是否设置了它的值?

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


顺便说一下。这是A类的定义

public class A
{
    public string SomeProp;
}

9 个答案:

答案 0 :(得分:17)

在.Net中,除非您明确使用refout关键字,否则所有都会按值传递。对于引用类型,这意味着传递引用的副本。

在你的第一个例子中,这意味着你的p变量仍然是对同一个对象的引用,因此设置一个属性就像你期望的那样工作。但是当你将引用本身设置为null时,所有你改变的都是副本。

答案 1 :(得分:5)

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参数来改变此行为。这会改变它,使它们 相同的引用,并且改变一个的值会改变另一个的值。

答案 2 :(得分:4)

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

您的方法包含引用的副本,因此,当您说p = null时,您只是更改该引用的副本。当您返回时,原始参考“p”仍然具有原始值。

在第二个示例中,您明确地通过引用传递对象。因此,新引用(null)将传递回您的调用函数(然后它会为您提供null异常)。

答案 3 :(得分:3)

p参数是对您所创建的新Foo实例的引用的副本。把它想象成一个路标:调用“new A()”在堆上创建一个A对象,然后返回一个指向它的路标,存储在p中。然后调用Foo函数,并给它一个路标副本 - 它知道如何到达A对象,并更新属性。然后它遍布整个路标 - 它不能再到那里,但物体仍然存在。调用者仍然有一个有效的路标,因此不会抛出任何异常。

第二个例子,“ref”参数有效地说“不要给我一个路标副本,给我实际的路标”。这一次,当它遍布它时,传递给函数的原始文件也会丢失,并且会发生异常。

在更多技术术语中,如果没有ref关键字,C#始终是“按值调用” - 所有参数都是参数值的传递副本。参数的值是一个路标(或“参考”)这一事实并不重要。

答案 4 :(得分:2)

这是一篇文章,其中包含您所看到的内容的图形表示:

Parameter passing in C# (Lee Richardson)

我发现这篇文章真有帮助。

我很惊讶没有人关联Jon Skeet的文章(我从中找到了上面的链接):

Parameter passing in C# (Jon Skeet's blog)

答案 5 :(得分:1)

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

答案 6 :(得分:1)

除非您使用out / ref,否则C#会使用传递值。按值传递引用时。复制参考。但是,由于它仍引用堆上的同一对象,因此可以通过引用修改对象的状态。如果您使用ref传递参考地址。取消此引用会使实际引用为空,因此在此之后访问原始引用时为NullReferenceException

答案 7 :(得分:0)

如果在调试时为监视列表添加“p”并为其生成对象ID,该怎么做呢,该id与函数Foo(p)中的“p”的id相同。因此,调用方法中的“p”和Foo中的“p”基本上都具有相同的对象ID。

答案 8 :(得分:-1)

当您通过valule传递对象引用时,您可以更改其状态。但是,当您通过引用传递对象引用时,您不仅可以更改其状态,还可以更改实际对象本身。

已编辑:知道对象永远不会作为值或引用传递,但对对象的引用将作为值或引用传递。