为什么list在不带ref的情况下传递给函数时表现得像带ref的传递
本文关键字:ref 函数 情况下 为什么 list | 更新日期: 2023-09-27 18:07:58
如果我没有弄错的话,这种行为对我来说很奇怪。与其解释,我将在下面发布一个示例代码,请告诉我为什么我得到的输出是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()); // output is 5
}
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()); // output is 5
}
private void Fuss(ref List<int> l)
{
l.Add(4);
l.Add(5);
}
它不像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);
}
试试。你注意到区别了吗?
你只能在没有ref的情况下传递list(或任何引用类型)的内容(因为正如其他人所说,你传递的是对堆上对象的引用,从而改变了相同的"内存")。
但是你不能改变"list", "list"是一个指向list类型对象的变量。只有通过引用传递时才能更改"list"(使其指向其他地方)。您将获得引用的副本,如果更改了该副本,则只能在方法中观察到。
参数在c#中是按值传递的,除非它们被标记为ref
或out
修饰符。对于引用类型,这意味着引用是按值传递的。因此,在Fuss
中,l
与其调用者引用List<int>
的同一个实例。因此,对List<int>
实例的任何修改都将被调用者看到。
现在,如果将参数l
标记为ref
或out
,则该参数通过引用传递。这意味着在Fuss
中,l
是存储位置的别名,用作调用方法的参数。说明:
public void Fuss(ref List<int> l)
,
List<int> list = new List<int> { 1, 2, 3 };
Fuss(list);
在Fuss
中,l
是list
的别名。特别是,如果将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 };
}
和call by
List<int> list = new List<int> { 1, 2, 3 };
Fuss(list);
则调用者仍然会看到list
有三个元素。
清楚了吗?
ByRef和ByVal仅适用于值类型,而不适用于引用类型,后者总是像" ByRef "一样传递。
如果您需要谨慎地修改列表,请使用". tolist()"函数,您将获得列表的克隆。
请记住,如果列表包含引用类型,则"新"列表包含指向原始列表中相同对象的指针。
列表已经是引用类型,因此当您将它们传递给方法时,您正在传递引用。任何Add
调用都会影响调用方中的列表。
通过ref
传递List<T>
的行为本质上就像传递一个指向该列表的双指针。下面是一个例子:
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);
}
}
类型为"List"的变量、参数或字段,或任何其他引用类型,实际上并不包含列表(或任何其他类的对象)。相反,它将保存像"对象ID #29115"这样的东西(当然,不是这样一个实际的字符串,而是一个比特的组合,本质上意味着)。在其他地方,系统将有一个被索引的对象集合,称为堆;如果某个List类型的变量持有"对象ID #29115",那么堆中的对象#29115将是List或其派生类型的实例。
如果MyFoo是List类型的变量,像'MyFoo. add ("George")'这样的语句实际上不会改变MyFoo;相反,它的意思是"检查存储在MyFoo中的对象ID,并调用存储在其中的对象的"Add"方法。"如果MyFoo在语句执行之前持有"对象ID #19533",它将在语句执行之后继续持有"对象ID #19533",但是对象ID #19533将调用其Add方法(可能会更改该对象)。相反,像"MyFoo = MyBar"这样的语句将使MyFoo持有与MyBar相同的对象id,但实际上不会对所讨论的对象做任何事情。如果MyBar在语句之前保存了"Object ID #59212",那么在语句之后,MyFoo也将保存"ObjectId #59212"。对象ID# 19533和对象ID#59212都不会发生任何变化。
对于像List这样的引用类型,ref和非ref之间的区别不在于是否传递引用(这种情况经常发生),而在于该引用是否可以更改。试试下面的
private void Fuss(ref List<int> l)
{
l = new List<int> { 4, 5 };
}
,你会看到计数是2,因为函数不仅操作了原始列表,还操作了引用本身。
只有int、double等基本类型是按值传递的。
复杂类型(如list)通过引用传递(确切地说,这是一个按值传递的指针)。
让我们解释一下Easy:)
- "Fuss"在你的第一个代码块中知道他应该接受一个来自列表类型 的引用
- "Fuss"在第二段代码中知道他应该接受nothing!而只是引用了"sth else"而这恰好是"sth else"在你的第一个代码块中是一样的 ♥
如果你觉得困惑,我认为你应该深刻理解什么是引用