为什么列表在没有引用的情况下传递给一个像ref一样传递的函数?

时间:2011-09-06 14:33:31

标签: c# c#-4.0 argument-passing ref

如果我没有得到这个非常错误,这种行为对我来说很奇怪。而不是解释,我将在下面发布一个示例代码,请告诉我为什么我得到输出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);
    }

7 个答案:

答案 0 :(得分:14)

它不像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);
}

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

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

但是,您无法更改“list”,“list”是指向List类型的对象的变量。如果您通过引用传递它,则只能更改“list”(以使其指向其他位置)。您将获得参考的副本,如果更改,则只能在您的方法中观察到。

答案 1 :(得分:6)

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

现在,如果您使用lref标记参数out,则参数将通过引用传递。这意味着在Fuss中,l是存储位置的别名,用作调用方法的参数。要明确:

public void Fuss(ref List<int> l)

通过

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

现在,在Fuss中,llist的别名。特别是,如果您将List<int>的新实例分配给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有三个元素。

清除?

答案 2 :(得分:3)

引用类型(如List)的ref和非ref之间的区别不在于您是否传递引用(始终发生),而是该引用是否可以更改。请尝试以下

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

并且您将看到计数为2,因为该函数不仅操纵原始列表而且操纵参考本身。

答案 3 :(得分:2)

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

通过List<T>传递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);
    }
}

答案 4 :(得分:2)

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

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

请记住,如果您的列表包含引用类型,则“新”列表包含指向原始列表所用对象的指针。

答案 5 :(得分:2)

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

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

答案 6 :(得分:0)

只有int,double等原始类型按值传递。

复杂类型(如列表)通过引用传递(这是一个按值传递指针,确切地说)。