17

如果我没有弄错这个严重的错误,那么这种行为对我来说很奇怪。我将在下面发布示例代码,而不是解释,请告诉我为什么我得到输出 x 而不是 y。

    private void button1_Click(object sender, EventArgs e)
    {
        List<int> l = new List<int>() { 1, 2, 3 };
        Fuss(l);
        MessageBox.Show(l.Count.ToString());
    }

    private void Fuss(List<int> l)
    {
        l.Add(4);
        l.Add(5);
    }

我假设输出应该是 3。但我得到的输出是 5。我知道如果我这样做,输出可以是 5:

    private void button1_Click(object sender, EventArgs e)
    {
        List<int> l = new List<int>() { 1, 2, 3 };
        Fuss(ref l);
        MessageBox.Show(l.Count.ToString());
    }

    private void Fuss(ref List<int> l)
    {
        l.Add(4);
        l.Add(5);
    }
4

7 回答 7

17

它不像 ref 传递的那样。

void ChangeMe(List<int> list) {
  list = new List<int>();
  list.Add(10);
}
void ChangeMeReally(ref List<int> list) {
  list = new List<int>();
  list.Add(10);
}

试试看。你注意到区别了吗?

如果您在没有 ref 的情况下传递列表(或任何引用类型)的内容(因为正如其他人所说,您正在传递对堆上对象的引用并因此更改相同的“内存”),则只能更改列表的内容(或任何引用类型)。

但是你不能改变“list”,“list”是一个指向List类型对象的变量。如果您通过引用传递“列表”(使其指向其他地方),您只能更改“列表”。您将获得一份参考副本,如果更改,只能在您的方法中观察到该副本。

于 2011-09-06T14:39:26.940 回答
7

参数在 C# 中按值传递,除非它们用reforout修饰符标记。对于引用类型,这意味着引用是按值传递的。因此, in指的是与其调用者相同的Fuss实例。因此,调用者将看到对此实例的任何修改。lList<int>List<int>

现在,如果您lrefor标记参数out,则参数通过引用传递。这意味着 inFussl存储位置的别名,用作调用方法的参数。要清楚:

public void Fuss(ref List<int> l)

List<int> list = new List<int> { 1, 2, 3 };
Fuss(list);

现在, inFussl的别名list。特别是,如果您分配List<int>to的新实例l,调用者也会看到分配给变量的新实例list。特别是,如果你说

public void Fuss(ref List<int> l) {
    l = new List<int> { 1 };
}

那么调用者现在将看到一个包含一个元素的列表。但如果你说

public void Fuss(List<int> l) {
    l = new List<int> { 1 };
}

并致电

List<int> list = new List<int> { 1, 2, 3 };
Fuss(list);

那么调用者仍然会看到list具有三个元素。

清除?

于 2011-09-06T14:36:18.817 回答
3

“List”类型的变量、参数或字段,或任何其他引用类型,实际上并不包含列表(或任何其他类的对象)。相反,它将包含类似“Object ID #29115”的内容(当然,不是这样的实际字符串,而是位的组合,这基本上意味着)。在其他地方,系统将有一个称为堆的对象的索引集合;如果某个 List 类型的变量持有“对象 ID #29115”,那么堆中的对象 #29115 将是 List 的实例或从其派生的某种类型。

如果 MyFoo 是 List 类型的变量,则像 'MyFoo.Add("George")' 这样的语句实际上不会改变 MyFoo;相反,它的意思是“检查存储在 MyFoo 中的对象 ID,并调用存储在其中的对象的“Add”方法。如果 MyFoo 在执行语句之前持有“Object ID #19533”,它会在之后继续这样做,但 Object ID #19533 将调用其 Add 方法(可能会更改该对象)。相反,像“MyFoo = MyBar”这样的语句将使 MyFoo 拥有与 MyBar 相同的 object-id,但实际上不会对问题。如果 MyBar 在语句之前持有“Object ID #59212”,那么在语句之后,MyFoo 也将持有“ObjectId #59212”。对象 ID #19533 和对象 ID#59212 都不会发生任何事情。

于 2011-11-16T18:20:46.963 回答
3

对于像 List 这样的引用类型,ref 和 non-ref 之间的区别不在于您是否传递了引用(这种情况总是会发生),而是该引用是否可以更改。尝试以下

private void Fuss(ref List<int> l)
{
    l = new List<int> { 4, 5 };
}

您会看到计数为 2,因为该函数不仅操作了原始列表,还操作了引用本身。

于 2011-09-06T14:37:14.400 回答
2

ByRef 和 ByVal 仅适用于值类型,而不适用于引用类型,它们总是像“byref”一样传递。

如果您需要谨慎修改列表,请使用“.ToList()”函数,您将获得列表的克隆。

请记住,如果您的列表包含引用类型,那么您的“新”列表包含指向与原始列表相同的对象的指针。

于 2011-09-06T14:42:30.177 回答
2

列表已经是引用类型,因此当您将它们传递给方法时,您就是在传递一个引用。任何Add调用都会影响调用者中的列表。

传递List<T>by 的ref行为本质上类似于将双指针传递给该列表。这是一个插图:

using System;
using System.Collections.Generic;

public class Test
{
    public static void Main()
    {
        List<int> l = new List<int>() { 1, 2, 3 };
        Fuss(l);
        Console.WriteLine(l.Count); // Count will now be 5.

        FussNonRef(l);
        Console.WriteLine(l.Count); // Count will still be 5 because we 
                                    // overwrote the copy of our reference 
                                    // in FussNonRef.

        FussRef(ref l);
        Console.WriteLine(l.Count); // Count will be 2 because we changed
                                    // our reference in FussRef.
    }

    private static void Fuss(List<int> l)
    {
        l.Add(4);
        l.Add(5);
    }

    private static void FussNonRef(List<int> l)
    {
        l = new List<int>();
        l.Add(6);
        l.Add(7);
    }

    private static void FussRef(ref List<int> l)
    {
        l = new List<int>();
        l.Add(8);
        l.Add(9);
    }
}
于 2011-09-06T14:35:35.557 回答
0

只有像 int、double 等原始类型是按值传递的。

复杂类型(如列表)通过引用传递(准确地说,它是按值传递的指针)。

于 2011-09-06T14:37:27.000 回答