由ref传递的列表 - 帮我解释一下这种行为

时间:2010-11-30 06:49:10

标签: c# list pass-by-reference

看看以下程序:

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

8 个答案:

答案 0 :(得分:185)

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

Init states

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

最后,当您执行:myList' = myList2时,您丢失了一个引用而不是原始引用,并且该集合保持排序。

Lost reference

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

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

答案 1 :(得分:98)

您正在将引用传递给列表,但您的通过引用传递列表变量 - 所以当您致电{ {1}}变量的(即引用 - 思考“指针”)被复制 - 并更改为ChangeList内的参数的未被ChangeList 看到。

尝试:

TestMethod

然后传递对本地变量 private void ChangeList(ref List<int> myList) {...} ... ChangeList(ref myList); 的引用(如myRef中所声明的那样);现在,如果您在TestMethod内重新分配参数,那么您也会在 ChangeList内重新分配变量

答案 2 :(得分:18)

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

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

  • 在C#中,您永远不会传递对象,而是按值传递其引用。

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

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

答案 3 :(得分:9)

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

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

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

答案 4 :(得分:5)

C#只是在传递值时执行浅拷贝,除非有问题的对象执行ICloneable(显然List类没有)。

这意味着它复制List本身,但对列表中对象的引用保持不变;也就是说,指针继续引用与原始List相同的对象。

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

阅读this MSDN article on "Passing Parameters"中的传递参考类型参数部分以获取更多信息。

来自StackOverflow的

"How do I Clone a Generic List in C#"讨论了如何制作List的深层副本。

答案 5 :(得分:3)

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

这是我的推理...... 即使list是通过引用传递的,也可以将其视为按值传递指针变量 当您调用ChangeList(myList)时,您将指针传递给(Global)myList。现在这存储在(local)myList变量中。所以现在你的(本地)myList和(全局)myList指向同一个列表。 现在你做一个sort =&gt;它的工作原理是因为(本地)myList引用了原始(全局)myList 接下来,您将创建一个新列表并将指针指定给您的(本地)myList。但是一旦函数退出(local),myList变量就会被销毁。 HTH

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;
    }
}

答案 6 :(得分:2)

使用ref关键字。

查看最终参考here以了解传递参数 具体来说,请查看this,以了解代码的行为。

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

使用ref可以更改指向您案例中List的新实例的引用的指针。如果没有ref,您可以处理现有参数,但不能指向其他参数。

答案 7 :(得分:0)

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

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