C#将List<T>作为引用传递给方法还是作为副本传递?

26

我从C/C++转到C#领域,对细节还有点模糊。据我所知,类默认情况下是通过引用传递的,但像List<string>这样的对象呢?比如:

void DoStuff(List<string> strs)
{
    //do stuff with the list of strings
}

以及其他地方

List<string> sl = new List<string>();
//next fill list in a loop etc. and then do stuff with it:
DoStuff(sl);

在这种情况下,sl是按引用传递还是会被复制一份, 因此我需要重新定义worker函数,例如
void DoStuff(ref List<string> strs)
来实际操作sl本身而不是它的一个副本?

8
class 通过引用传递,struct 通过值传递。 - Dialecticus
1
这篇文章应该能帮助你更好地理解这个领域。 - Kurubaran
似乎C#和C ++中的“按引用传递”来自不同的字典。无论如何,感谢所有答案 - 模糊的细节现在更清晰了。 - Scre
有些人由于术语“引用”和“引用类型”(确实指的是不同的“引用”含义)的缘故,在C#中混淆使用“按引用传递”的概念。但在C#中正确地使用“按引用传递”,即采用ref参数,与C++和CS中一般所称的“按引用传递”基本相同。 - user395760
7个回答

30

它是通过引用传递的。List<T> 是一个类,所有类实例都是通过引用传递的。


11

行为始终相同:通过复制进行传递。如果参数是一个对象,则会复制对该对象的引用,因此实际上您正在操作同一个对象/列表/其他。


8
除了其他答案,理解ref的行为非常重要。
以下是一些演示目的的示例代码。
static void Main(string[] args)
    {

        List<string> lstStr = new List<string>();

        lstStr.Add("First");
        lstStr.Add("Second");

        Alter(lstStr);

        //Alter(ref lstStr);

        Console.WriteLine("---From Main---");
        foreach (string s in lstStr)
        {
            Console.WriteLine(s);
        }

        Alter2(ref lstStr);

        Console.WriteLine("---From Main after passed by ref---");
        foreach (string s in lstStr)
        {
            Console.WriteLine(s);
        }

        Console.ReadKey();
    }

    static void Alter(List<string> lstStr2)
    {
        lstStr2.Add("Third");

        Console.WriteLine("----From Alter----");
        foreach (string s in lstStr2)
        {
            Console.WriteLine(s);
        }

        lstStr2 = new List<string>();
        lstStr2.Add("Something new");

        Console.WriteLine("----From Alter - after the local var is assigned somthing else----");

        foreach (string s in lstStr2)
        {
            Console.WriteLine(s);
        }

    }

    static void Alter2(ref List<string> lstStr2)
    {
        lstStr2 = new List<string>();
        lstStr2.Add("Something new from alter 2");

        Console.WriteLine("----From Alter2 - after the local var is assigned new list----");

        foreach (string s in lstStr2)
        {
            Console.WriteLine(s);
        }

    }


//----From Alter----
//First
//Second
//Third
//----From Alter - after the local var is assigned somthing else----
// Something new
// ---From Main---
// First
// Second
// Third
// ----From Alter2 - after the local var is assigned new list----
// Something new from alter 2
// ---From Main after passed by ref---
// Something new from alter 2

5
底层的东西总是这样的:值类型按值传递,引用类型按“引用传递”(加引号是因为引用的值实际上是按值传递的,但大多数人为了简洁起见忽略了这一点)。
解决“ref”关键字与引用之间的矛盾最简单的方法是:引用类型通过值传递它们的引用。在标准情况下,这会导致将引用传递给列表(而不是整个列表)到方法中。
当在引用类型上使用“ref”关键字时,语义上会将引用传递给引用(我真的很难不说“指向指针”)。
如果您的方法要将“ref”参数重新分配给一个新对象,则调用者也会看到这个新分配。而没有“ref”关键字,该方法只会重新分配它们自己的本地副本的引用值,调用者仍然会引用他们原始的对象。
以上解释无耻地摘自 Jon Skeet关于此主题的文章
这种差异对于理解C#中的参数传递至关重要,这就是为什么我认为默认情况下说对象按引用传递是高度混淆的,正确的陈述应该是对象引用按值传递。

只有在您打算重新分配参数并将其显示给调用者时,才需要使用ref关键字。 在大多数情况下,您会发现它是不必要的。 您可以重新编写DoStuff以删除它,并成功地按值传递列表的引用:

void DoSomething(List<string> strs) 
{ 
    strs.Add("Hello");
}

这是我对它如何工作的第一个“直觉”,但并不确定。 - Scre
@scre 我强烈推荐阅读Jon Skeet的文章,并购买他的C#深度剖析书籍,他有很好的技巧来构建抽象或复杂主题的解释,以便更容易理解。 - Adam Houldsworth

3
如果您想修改原始列表,则方法中的“ref”关键字是多余的:List<T>是一个引用类型(在C#中是“class”),因此将通过引用传递给该方法;因此,该方法将操作原始列表。
当传递“值类型”时,它将创建值本身的副本。 当传递“引用类型”时,它将创建引用的副本。
在C#中了解更多关于“值类型”和“引用类型”的信息,请阅读Value and Reference Types

5
ref 不是多余的。有了它,strs = ...; 会影响调用者;没有它,就不会影响调用者。 - user395760
2
此外,如果在方法中发送一个引用类型对象而没有使用 ref 关键字,则会创建该引用的新副本。"冗余" 意味着它是相同的,但实际上并不是。 - Tarec

2

该列表是按引用传递的。这意味着方法内的strs变量实际上指向与方法外的sl变量相同的列表。 如果您使用ref,实际上可以在方法内重新分配sl变量。

strs = new List<string>()

会使sl指向新列表。

如果你有C/C++的经验:ref可以被视为“安全指针”。这类似于使用&strs。


-2

它是通过引用传递的。不需要包含 "ref"。

最好的问候。


4
“ref”不是多余的。有了它,“strs = ...;” 将会影响调用者。没有它,就不会产生影响。 - user395760
请提供需要翻译的英文内容。 - fabian

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