为什么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);
    }

为什么list在不带ref的情况下传递给函数时表现得像带ref的传递

它不像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#中是按值传递的,除非它们被标记为refout修饰符。对于引用类型,这意味着引用是按值传递的。因此,在Fuss中,l与其调用者引用List<int>的同一个实例。因此,对List<int>实例的任何修改都将被调用者看到。

现在,如果将参数l标记为refout,则该参数通过引用传递。这意味着在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 };
}

和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"在你的第一个代码块中是一样的 ♥

如果你觉得困惑,我认为你应该深刻理解什么是引用