值类型和引用类型问题

3

你好,我正在尝试对两个对象进行简单的交换。我的代码是:

void Main()
{
  object First = 5;
  object Second = 10;

  Swap(First, Second);
  //If I display results it displays as 
  //Value of First as 5 and Second as 10
}

private static void Swap(object First, object Second)
{
  object temp = First;
  First = Second;
  Second = temp;
}

由于对象是引用类型,应该将其引用传递给方法并进行交换。 为什么没有发生呢?


1
建议你阅读 http://www.yoda.arachsys.com/csharp/parameters.html 以了解这里正在发生的事情,因为术语非常重要(而且下面一半的答案都是错误的)。 - fearofawhackplanet
7个回答

6

这样看待它。

让我们将您的交换方法中的参数重命名为“x”和“y”,因为使它们与本地变量相同只会令人困惑。

你有一个苹果和一个橙子。两个对象。

你有两张纸,上面写着“APPLE”和“ORANGE”。这些是对对象的引用。

你有两个盒子,一个标记为First,一个标记为Second。

你把“APPLE”放在First里,把“ORANGE”放在second里。

现在你获得了另外两个标有x和y的盒子。

你复制了APPLE并把它放在x中。你复制了ORANGE并把它放在y中。

现在你交换了x和y的内容。APPLE和ORANGE仍然是引用,但你没有改变First和Second的内容。你改变了x和y的内容。

现在假设你在参数x和y的声明中添加“ref”。这意味着:

你有两个盒子,一个标记为First,一个标记为Second。

你把“APPLE”放在First里,把“ORANGE”放在second里。

你给First添加了一个新标签;它现在也可以被称为x。Second也可以被称为y。

你交换了x和y的内容。由于它们只是First和Second的别名,所以First和Second的内容被交换了。

有意义吗?


5

这里有几个不同的东西:

  • 对象存储在堆中,请暂时忽略它们;
  • FirstSecond中存储的是对对象引用
  • Main中有两个变量
  • Swap中有两个参数

现在,重要的事情是"引用类型/引用"和"按引用传递"之间的区别。它们完全无关

在下面这行代码中:

Swap(First, Second);

你将两个变量的值传递给了“Swap”函数。在这种情况下,“First” / “Second”的值是指向封装对象的引用。请注意,保留了HTML标签。
接下来:
private static void Swap(object First, object Second)
{
  object temp = First;
  First = Second;
  Second = temp;
}

在这里,你交换了两个本地参数的值,但是它们完全独立于任何其他事情。如果我们想让调用者看到的改变(即重新赋值),我们需要通过引用传递

private static void Swap(ref object First, ref object Second)
{
  object temp = First;
  First = Second;
  Second = temp;
}

现在,First的值不再是对装箱对象的引用;它是对装箱对象的引用的引用。在调用者处,我们使用:

Swap(ref First,ref Second);

这意味着要传递变量First的引用,而不是值。

请注意,我说过您可以忘记存在对象这一事实吗?如果我们使用以下代码,所有内容都完全相同:

int x = 1, y = 2;
Swap(ref x, ref y);
void Swap(ref int a, ref int b) {
    var tmp = a; a = b; b = tmp;
}

唯一的区别在于,x 为 1 等等,而 ref x 是对变量 x 的引用。在按引用传递时,引用类型与值类型完全无关;唯一重要的是理解默认情况下传递变量的 ,其中变量的 1(等等)或“对象的引用”。无论哪种方式,逻辑都是相同的。


如果我必须将对象作为 ref 传递,值类型和引用类型之间有什么区别? - Umesh CHILAKA
@Chikaka - 这可能听起来有点循环,但是“值类型语义与引用类型语义”。我的意思是:一个标准变量/参数包含了:a:值本身(值类型),还是b:对象的引用(引用类型)。作为其副作用:多个变量可以引用相同的引用类型,而对于值类型,结构体在每次分配时都会被复制。在您的情况下,您有一个装箱对象,它是引用类型。但关键在于您的Swap 重新分配了参数。要观察到这一点,您需要通过引用传递。 - Marc Gravell
类GraphPoint { 公共int X {get; set;} 公共int Y {get; set;} } void button1_Click(object sender, EventArgs e) { GraphPoint First = new GraphPoint(){X = 1,Y = 1}; GraphPoint Second = new GraphPoint(){X = 2,Y = 2}; 修改(第一); //如果我显示结果,则显示 //第一个值为5 } void Mofiy(GraphPoint First) { First.X = 5; First.Y = 5; } 如果它不传递引用,那么在调用修改方法后,Main中的First值如何更改。 - Umesh CHILAKA
@Chilaka - 注意我两次使用了“重新分配”这个词吗?关键在于,在Swap中,你给参数赋予不同的值——First = ...。在你刚刚给出的例子中,你只是改变了对象的属性。请注意,发生更改的位置现在完全不同——引用(参数的值)未更改,但堆上的远程对象已更改。如果你只是改变对象,调用者会自动看到这一点——毕竟,它就是同一个对象 - Marc Gravell
感谢您帮助我更好地理解它。 - Umesh CHILAKA

1

你没有将其作为参考传递,你需要明确声明你正在传递引用:

 Swap(ref First,ref Second);

 private static void Swap(ref object First,ref object Second)

如果我必须通过 ref 关键字显式传递参数,那么值类型和引用类型之间有什么区别? - Umesh CHILAKA
因为它在另一个方法中,所以你必须明确地表示你想传递引用,而如果它在同一个方法中,它就会起作用。大多数情况下,您不需要传递引用,只需要传递对象的值,这就是为什么您必须将其指定为引用的原因。 - TBohnen.jnr
尝试使用以下类point{public int X;},现在将类point的对象传递给另一个方法(比如modify),并在该函数中更改其X值。在您的主方法中,在调用modify方法后,检查X的值,它应该已经改变了(不要通过引用传递)。这是如何发生的? - Umesh CHILAKA
我认为这是深拷贝与浅拷贝的问题,可以参考这里:https://dev59.com/r3VC5IYBdhLWcg3wykUt。在修改方法中,将点类赋值给一个新的点类,然后更改X的值,您会发现它没有更改初始类的值。 - TBohnen.jnr
我建议你把那个问题提给 @Mark Gravell,这样他可以确认一下。 - TBohnen.jnr

1

与其按值传递对象的引用,您需要按引用传递它们。换句话说,您需要传递对象引用的引用

这可以使用ref关键字实现。

例如:

private static void Swap(ref object First, ref object Second)
{
  object temp = First;
  First = Second;
  Second = temp;
}

2
这里的措辞有点不太对,我认为你永远不应该传递对象;当你使用 ref object 时,你应该传递引用 - Marc Gravell
@Marc:抱歉,我会重新措辞! - Nick

1

你正在通过值传递对象的引用(在C#中,按值传递是默认的)。

相反,你需要通过引用传递对象到函数中(使用ref关键字)。

private static void Swap(ref object first, ref object second)
{
    object temp = first;
    first = second;
    second = temp;
}

void Main()
{
    object first = 5;
    object second = 10;

    Swap(ref first, ref second);
}

当然,如果您使用的是C# 2.0或更高版本,则最好定义一个泛型版本的此函数,该函数可以接受任何类型的参数,而不是object类型。例如:

private static void Swap<T>(ref T first, ref T second)
{
    T temp;
    temp = first;
    first = second;
    second = temp;
}

然后你也可以使用它们的正确类型,int,来声明变量。


1

Swap 方法内参数所指向的对象是 Main 内的 FirstSecond 对象的引用,但这些参数本身只在 Swap 方法内部有效。

因此,如果你在 Swap 方法内部编写了 First = 1; Second = 2;,你会看到 Main 内的对象发生变化。然而,你只是改变了 Swap 的参数所指向的对象(通过将它们赋值给另一个对象),并没有实际改变这些对象。如果你试图在 Swap 方法内部将对象设置为 null,也会出现同样的情况。


0

你需要通过引用传递。

关于按引用传递和按值传递的一些额外信息: http://www.yoda.arachsys.com/csharp/parameters.html, 可以尝试这个:

void Main()
{
 object First = 5;
 object Second = 10;

  Swap(ref First, ref Second);
  //If I display results it displays as 
  //Value of First as 5 and Second as 10
}


private static void Swap(ref object First, ref object Second)
{
  object temp = First;
  First = Second;
  Second = temp;
}

2
你需要在方法声明中添加引用。 - TBohnen.jnr

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