按值传递和按引用传递

5

请您解释一下C#类的以下行为。我期望classResult的值是"Class Lijo",但实际值是“Changed”。

我们在这里复制了引用。尽管复制品指向同一个地址,但接收参数的方法不能改变原始数据。

为什么值还是改变了呢?

public partial class _Default : Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        String nameString = "string Lijo";

        Person p = new Person();
        p.Name = "Class Lijo";

        Utilityclass.TestMethod(nameString, p);
        string classResult = p.Name;
        Response.Write(nameString + "....." + classResult);
    }
}

public class Utilityclass
{
    public static void TestMethod(String nameString, Person k)
    {
        nameString = "Changed";
        k.Name = "Changed";
    }
}

public class Person
{
    public string Name
    {
        get; set;
    }
}
更新:当我传递一个字符串时,它实际上并没有被改变。

3
我建议你阅读Mr Skeet在这个主题上的优秀文章 - Alconja
尽管String是一个对象,但它是不可变的,因此当您传递一个字符串时,原始引用不会被修改。 - trendl
我已经阅读了这篇文章。本文将向您展示内存中如何处理参数传递。 - Developex
6个回答

22

最简短的回答是:阅读我的文章,关于参数传递。该文章详细介绍了这个问题。

稍微长一点的回答是比较这两种方法,它们都使用参数:

public void ChangeMe(string x)
{
    x = "changed";
}

public void ChangeMe(Person x)
{
    x.Name = "changed";
}
在第一种情况下,您正在更改参数的值。这与原始参数完全隔离。您无法更改字符串本身的内容,因为字符串是不可变的。
在第二种情况下,您正在更改参数值所引用对象的内容。这不会改变参数本身的值-它仍将是相同的引用。举个现实世界的例子,如果有人将东西送到您的家中并更改了您家中的内容,但并未更改您家的地址。
如果您将第二种方法更改为以下内容:
public void ChangeMe(Person x)
{
    x = new Person("Fred");
}

那么调用者将不会看到任何变化。这更接近于你使用字符串时所做的操作,即使参数引用不同的对象,而不是改变现有对象的内容。

现在,当您使用ref参数时,调用者使用作为参数的变量与参数进行“别名” - 因此,如果您更改参数的值,则也会更改参数的值。因此,如果我们像这样更改最后一个方法:

public void ChangeMe(ref Person x)
{
    x = new Person("Fred");
}

那么:

Person y = new Person("Eric");
ChangeMe(ref y);
Console.WriteLine(y.Name);

这将打印出"Fred"。

理解的关键概念是变量的值永远不是一个对象 - 它要么是值类型的值,要么是引用。如果更改了对象的数据,该更改将通过其他引用可见。一旦您明白复制引用并不等同于复制对象,其余部分就相对容易理解。


谢谢。我的大脑现在轻松多了。我可以问以下问题吗?1)我能使我的自定义类不可变吗?我能将我的类作为值类型(如int)吗?3)对象和类型之间有什么区别?【“变量的值从来不是一个对象 - 它要么是值类型的值,要么是引用”】 - Lijo
2
  1. 是的,你绝对可以创建自己的不可变类。只要不提供任何突变方式即可。
  2. 你可以创建自己的自定义值类型,但它们不会是类 - 它们将是结构体。
  3. 我不确定我真正理解这个问题 - 但值类型值就像“数字3”或“字母A”,而引用是一种寻址对象的方式,基本上是一种额外的间接级别。
- Jon Skeet
谢谢。以下是翻译的文本:请确认以下内容是否正确?
  1. 当一个类在方法中被传递时,如果没有使用“ref”,它会将内存地址复制到一个新的引用对象中。因此,如果对对象进行任何更新,则内存位置中的值将得到更新。
  2. 当一个类在方法中使用“ref”被传递时,它的实际引用被传递给方法。如果我使引用指向不同的位置,那么我们将获得访问新内存位置的权限。旧的内存位置变得无法访问。谢谢。
- Lijo
1
@Lijo:1. 并没有传递类,而是传递了一个引用。它不是一个引用对象,只是一个引用。没有额外的值被更新——只是通过一个变量所做的更改将通过其他引用相同对象的变量看到。2. 再次强调,不要认为传递给方法的是一个“类”。我通常认为传递的是一个变量,实际上...参数变量和用于参数的变量是别名。至于“无法访问”的内存——这实际上取决于具体情况。 - Jon Skeet

7

Person 是一个引用类型,所以无论你使用 refout 还是不用任何关键字,你都能在方法内部修改它。你从未将真正的 person 对象传递给方法,而是传递指针作为引用,而不是实际的 Personref 关键字对于值类型(如结构体、int、float、DateTime 等)非常有用。如果你将其与引用类型一起使用,它只能指示行为,但不能强制执行。如果你将其与引用类型一起使用,它允许你更改该引用所指向的对象。


4
我不确定你为什么说“ref关键字仅适用于值类型”。如果你将其与引用类型一起使用,它允许你更改参数所指向的对象,这在某些情况下非常有用。 - Mark Heath
1
并不完全正确,你仍然可以通过 ref 传递引用类型,并且它会有微妙的不同,因为它允许你更改原始引用指向的内容。 - Alconja
1
“它也可以与引用类型一起使用,但仅用于指示行为,而不能强制执行它。” - 不,绝对不是这样。使用“ref”与引用类型参数一起会以与值类型相同的方式更改行为:调用者将看到对参数值的更改。 - Jon Skeet
@Jon,我的意思是,即使没有使用ref关键字传递引用类型参数,它也可以在方法内部被修改。 - Darin Dimitrov
1
@Darin:是的,但你关于“指示行为”的措辞有误导性。我认为如果你删除那句话,你的回答会更好。特别是,你绝对不应该使用'ref'来表示对象的内容可能会改变 - 你应该使用它来表示方法希望能够改变参数本身的值。 - Jon Skeet
显示剩余2条评论

0

这个问题已经得到了大部分的回答,但我认为你可能想尝试一下这个代码片段(如果你使用整数进行尝试,那就更好了!)

class Program
{
    static void Main(string[] args)
    {            
        Person p = new Person();
        p.Name = "Class Lijo";

        Utilityclass.TestMethod(p);
        string classResult = p.Name;
        Console.WriteLine(classResult);
        Utilityclass.TestMethod2(ref p);
        classResult = p.Name;  // will bomb here           
        Console.WriteLine(classResult);
    }
}

public class Utilityclass
{
    public static void TestMethod(Person k)
    {
        k.Name = "Changed";
        k = null;
    }

    public static void TestMethod2(ref Person k)
    {
        k.Name = "Changed Again!";
        k = null;
    }
}

0
当你将P传递给一个测试方法时,实际上是传递了它在内存中的位置,而不是对象的副本。方法体中会获取到这个引用,并对原始值进行修改。

0

Utilityclass.TestMethod 无法更改本地变量 p 指向不同的 Person 对象,因为您没有通过引用传递,但它仍然可以自由调用任何方法或更改传递的对象的任何属性。因此,在 Utilityclass.TestMethod 中可以修改 Name 属性。


-1
当您将引用类型参数传递给方法时,这意味着该方法直接访问该参数而不是其副本...
因此,结果会发生更改。

不,它有参数的副本 - 但该参数是一个引用,而不是对象本身。 - Jon Skeet
你的意思是对象的地址被复制了...对吗? 我所说的直接访问和你的意思是一样的,不是吗? - odiseh

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