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

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

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

输出应该,我假设是3.但我得到输出为5.我理解输出可以是5如果我这样做:

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

它不像ref传递的那样行事。

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

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

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

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

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

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

 public void Fuss(ref List l) 

叫做

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

现在,在Fussllist的别名。 特别是,如果将List新实例分配给l ,则调用者也将看到分配给变量list新实例。 特别是,如果你说

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

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

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

并打电话给

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

然后调用者仍然会将list视为具有三个元素。

明确?

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

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

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

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

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

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

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

通过ref传递List的行为基本上就像将双指针传递给该列表一样。 这是一个例子:

 using System; using System.Collections.Generic; public class Test { public static void Main() { List l = new List() { 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 l) { l.Add(4); l.Add(5); } private static void FussNonRef(List l) { l = new List(); l.Add(6); l.Add(7); } private static void FussRef(ref List l) { l = new List(); l.Add(8); l.Add(9); } } 

“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都不会发生任何事情。

只有int,double等基本类型按值传递。

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