ref传递的列表-帮助我解释此行为


109

看一下以下程序:

class Test
{
    List<int> myList = new List<int>();

    public void TestMethod()
    {
        myList.Add(100);
        myList.Add(50);
        myList.Add(10);

        ChangeList(myList);

        foreach (int i in myList)
        {
            Console.WriteLine(i);
        }
    }

    private void ChangeList(List<int> myList)
    {
        myList.Sort();

        List<int> myList2 = new List<int>();
        myList2.Add(3);
        myList2.Add(4);

        myList = myList2;
    }
}

我以为myList会过去了ref,输出会

3
4

该列表确实是“由ref传递”的,但是只有该sort功能才能生效。以下语句myList = myList2;无效。

所以输出实际上是:

10
50
100

您能帮我解释一下这种行为吗?如果确实myList没有通过引用传递(因为它似乎myList = myList2没有生效),那么如何myList.Sort()生效?

我甚至假设该语句不生效,输出为:

100
50
10

只是一个观察(我意识到问题已经在这里简化了),但是如果实际上正在创建一个新列表,则似乎最好ChangeList返回a List<int>而不是a void
杰夫B

Answers:


110

您传递到列表中的参考,但你不能将列表传递变量的引用 -所以,当你调用ChangeList变量的值(即参考-认为“指针”)被复制-和更改的值里面ChangeList 参数看不到TestMethod

尝试:

private void ChangeList(ref List<int> myList) {...}
...
ChangeList(ref myList);

然后,它传递对局部变量的引用 myRef(如中所述TestMethod);现在,如果您在内部重新分配参数,那么您ChangeList也在内部 重新分配变量TestMethod


确实,我可以做到,但是我想知道排序的效果如何
nmdr

6
@Ngm-调用时ChangeList复制引用 -它是同一对象。如果您更改以某种方式的对象,一切都具有参考该对象将看到的变化。
马克·格拉韦尔

225

最初,它可以用图形表示如下:

初始化状态

然后,应用排序 myList.Sort(); 排序集合

最终,当您执行以下操作:时myList' = myList2,您丢失了一个参考,但没有丢失原始参考,并且该集合保持了排序状态。

遗失参考

如果您通过引用(ref)使用,则myList'myList将变为相同(仅一个引用)。

注意:我myList'用来表示您使用的参数ChangeList(因为您使用了与原始名称相同的名称)


20

这是一种了解它的简单方法

  • 您的列表是在堆上创建的对象。该变量myList是对该对象的引用。

  • 在C#中,您从不传递对象,而是按值传递对象的引用。

  • 当您通过传递的引用访问列表对象时 ChangeList(例如,在排序时),原始列表将被更改。

  • ChangeList方法的赋值是引用的值,因此不会对原始列表进行任何更改(仍在堆上,但不再在方法变量上进行引用)。


10

链接将帮助您理解C#中的引用传递。基本上,将引用类型的对象按值传递给方法时,只有该对象上可用的方法才能修改对象的内容。

例如,List.sort()方法更改List的内容,但是如果将其他对象分配给同一变量,则该分配是该方法的本地对象。这就是为什么myList保持不变的原因。

如果我们使用ref关键字传递引用类型的对象,则可以将其他一些对象分配给同一变量,这将更改整个对象本身。

(编辑:是上面链接的文档的更新版本。)


5

除非有问题的对象执行ICloneable(显然List不是该类),否则C#在按值传递时只会执行浅表复制。

这意味着它会复制List自身,但是对列表内对象的引用保持不变;也就是说,指针继续引用与original相同的对象List

如果更改新List引用的事物的值,则List也会更改原始对象(因为它引用了相同的对象)。但是,您随后将myList完全引用的内容更改为new List,现在只有原始List引用了这些整数。

有关更多信息,请从MSDN文章“传递参数”中阅读“ 传递引用类型参数”部分。

StackOverflow上的“如何在C#中克隆通用列表”讨论了如何制作列表的深层副本。


3

虽然我同意每个人的上述意见。我对此代码有不同的看法。 基本上,您是将新列表分配给局部变量myList而不是全局变量。 如果将ChangeList(List myList)的签名更改为私有void ChangeList(),则会看到输出3、4。

这是我的理由...即使列表是通过引用传递的,也应将其视为按值传递指针变量。当调用ChangeList(myList)时,您会将指针传递给(Global)myList。现在,它存储在(local)myList变量中。因此,现在您的(local)myList和(global)myList指向同一列表。现在,您进行排序=>即可,因为(local)myList引用了原始(global)myList,接下来,您将创建一个新列表,并将指针分配给您的(local)myList。但是一旦函数退出,(local)myList变量就会被销毁。高温超导

class Test
{
    List<int> myList = new List<int>();
    public void TestMethod()
    {

        myList.Add(100);
        myList.Add(50);
        myList.Add(10);

        ChangeList();

        foreach (int i in myList)
        {
            Console.WriteLine(i);
        }
    }

    private void ChangeList()
    {
        myList.Sort();

        List<int> myList2 = new List<int>();
        myList2.Add(3);
        myList2.Add(4);

        myList = myList2;
    }
}

2

使用ref关键字。

查看此处的权威参考以了解传递的参数。
具体来说,请看this,以了解代码的行为。

编辑:Sort在相同的引用(按值传递)上工作,因此值是有序的。但是,为参数分配新实例将不起作用,因为除非您放置,否则参数是通过值传递的ref

放置ref可让您将指向引用的指针更改为List您案例中的新实例。没有ref,您可以使用现有参数,但不能使其指向其他参数。


0

为引用类型的对象分配了两部分内存。一堆一堆。堆栈中的部分(也称为指针)包含对堆中部分的引用-实际值存储在堆中。

当不使用ref关键字时,仅创建堆栈中一部分的副本并将其传递给该方法-引用堆中的同一部分。因此,如果更改堆部分中的某些内容,这些更改将保留。如果更改了复制的指针(通过将其分配为引用堆中的其他位置),则不会影响方法外部的原始指针。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.