【问题标题】:In C# what is the benefit of passing value by ref?在 C# 中,通过 ref 传递值有什么好处?
【发布时间】:2020-11-29 17:09:00
【问题描述】:

我试图了解在 c# 中通过 ref 传递方法参数而不是通过值传递参数有什么好处。

我有一个自定义对象列表(大约 50k),我需要对其属性运行一些操作。我编写了一个计算类,它接受 50K 元素的列表并返回值。我想知道如果我通过引用传递参数,它是否会在运行时保存我的系统内存,因为我正在传递引用而不是传递 50k 列表的副本? .NET 实际上是如何维护这一点的?

main(){
    var itemList={}//list containing 50k items
    var result=Calculate(itemList);// it passes a copy of the array
    var resultByRef=Calculate(ref itemList); //**it passes address of result variable, is it going to take less memory in runtime??**
}
private int Calculate(List<CustomClass> itemList){
    //do some calculation
    return result;
}
private int CalculateByRef(ref List<CustomClass> itemList){
    //do some calculation
    return result;
}

【问题讨论】:

  • 即使没有ref,也不会复制,因为List&lt;&gt;reference type
  • 首先要了解value typereference type的区别;然后去看看关于这两种广泛类型的“by ref”传递之间的区别。当您“通过 ref”传递值类型时,这允许您更改值,并且该更改将在原始变量中的方法回调之外看到。当您“通过 ref”传递引用类型时,这真正意味着您可以将新对象分配给该变量,并且外部的原始变量也将指向新对象。 “通过 ref”传递引用类型并不常见。
  • Ref 关键字主要用于原始类型(int,float,etx..),因为 c# 中的所有对象都通过引用传递
  • 如果你使用 Parallel.For 等,你会发现循环内的代码不能通过 ref 变量传递给循环内调用的函数。

标签: c#


【解决方案1】:

看起来你像我一样来自 C++ 背景。

在 C# 中,每个对象都会被传递,但它的引用始终是它的意思,这意味着无论对象有多大,你总是将它的引用传递给方法。

ref 关键字的唯一区别是让您能够更改该引用本身。让我们通过一个例子来理解:

static void callByRef(ref byte[] buff)
{
    buff[0] = 10;
    buff = new byte[5];
}

static void callNormally(byte[] buff)
{
    buff[0] = 10;
    buff = new byte[5];
}

static void Main(string[] args)
{
    byte[] param = new byte[5];
    param[0] = 5;

    Console.WriteLine("Original param.Hash: " + param.GetHashCode());
    callNormally(param);
    Console.WriteLine("param[0]: " + param[0]);
    Console.WriteLine("param.Hash: " + param.GetHashCode());
    callByRef(ref param);
    Console.WriteLine("param[0]: " + param[0]);
    Console.WriteLine("param.Hash: " + param.GetHashCode());
    return;
}

输出如下:

Origenal param.Hash: 58225482
param[0]: 10
param.Hash: 58225482
param[0]: 0
param.Hash: 54267293

在正常调用中,您也可以更改对象内部的内容,但在ref调用的情况下,对象本身可以更改。

在您的情况下,您只担心内存复制,以防将大数据作为参数传递给 C++ 中发生的方法。在 C# 中并非如此。

【讨论】:

  • 明确一点,List&lt;T&gt; 不是链表;它是 T 类型项目数组的包装器。
  • 这个答案我很清楚。我看到了很多关于此的讨论,但没有人能以更好的方式区分它。所以你想说的是,如果主方法有一个大小为 10 的数组并且它调用另一个方法,那么该方法将无法更改该大小。如果我们传递该数组的 ref,那么外部方法也可以改变大小。这是使用 ref 的主要好处。你觉得我的理解好不好?
  • 它不仅改变了大小,还改变了对象本身。您可以检查param.GetHashCode() 而不是大小,如果通过ref,即使这样也会改变。我已经编辑了答案以便更好地理解
【解决方案2】:

在这里通过引用传递对您没有帮助。

这是因为按值传递引用类型列表的列表或数组仍然传递引用。不同之处在于,当您通过值传递时,您传递的是引用的副本(值),但只有引用被复制。当您通过引用传递时,您传递的是原始变量。

复制仅 20 字节的引用与您需要执行的实际引用传递工作并没有明显的不同,因此没有性能优势。仅当您需要更改变量本身时,通过引用传递才有用:例如,为其分配一个全新的 List 对象。

//pass itemList by value
private int Calculate(List<CustomClass> itemList)
{
    itemList[0] = new CustomClass(); // this still works!
    //The List reference that was passed to this method still refers
    // to the *same List object* in memory, and therefore if we update
    // the item at position [0] here it will still be changed after the 
    // method returns.


    // But this does NOT change the original after the method ends,
    // because itemList is a different variable and we just changed
    // it to refer to a whole new object.
    itemList = new List<CustomClass>(); 
    // If we had instead passed this by reference, then the calling code
    // would also see the brand new list.
}

【讨论】:

    【解决方案3】:

    让我们来看看下面的例子。

    static void Main(string[] args)
    {
        var myLocalList = new List<int> { 1, 2, 3 };    // 1
        myLocalList.ForEach(x => Console.WriteLine(x)); // 2
    
        Calculate1(myLocalList);                        // 3
        myLocalList.ForEach(x => Console.WriteLine(x)); // 5
    
        Calculate2(ref myLocalList);                    // 6
        myLocalList.ForEach(x => Console.WriteLine(x)); // 8
    }
    
    private void Calculate1(List<int> list)
    {
        list = new List<int> { 4, 5, 6 };               // 4
    }
    
    private void Calculate2(ref List<int> list)
    {
        list = new List<int> { 7, 8, 9 };               // 7
    }
    
    • 第 1 步创建一个本地整数列表,初始化为值 1、2 和 3。
    • 步骤 2 打印列表。控制台输出在不同的行上显示 1、2 和 3。
    • 第 3 步调用 Calculate1,并将本地列表作为参数输入。
    • 第 4 步为 list 变量分配一个新的整数列表,其中包含值 4、5 和 6。
    • 步骤 5 打印列表。控制台输出在不同的行上显示 1、2 和 3,与第 2 步相同。
    • 第 6 步调用 Calculate2,并将本地列表作为 ref 参数输入。
    • 第 7 步为 list 变量分配了一个新的整数列表,其值为 7、8 和 9。
    • 步骤 8 打印列表。这一次,控制台输出在不同的行显示 7、8 和 9。

    myLocalList 传递给Calculate1 时,不会复制列表。绝对清楚,我的具体意思是myLocalList内容NOT 复制到list 参数。然而,复制的是对myLocalList 的引用。换句话说,对myLocalList 的引用按值 复制到list 参数。当步骤 4 将 list 设置为新的 4-5-6 列表时,复制的引用(即 list)会被修改,而不是原始引用(即 myLocalList)。

    这随着Calculate2 而改变。在这种情况下,对myLocalList 的引用通过引用 传递给list 参数。这有效地将list 转换为myLocalList 的别名,这意味着当步骤7 将list 设置为新的7-8-9 列表时,原始引用(即myLocalList)被修改。这就是第 8 步中输出发生变化的原因。

    ...当我通过 引用而不传递 50k 列表的副本?

    没有。 CalculateCalculateByRef 方法都不会收到 itemList 的深层副本,因此性能不会像您建议的那样受到影响。使用CalculateByRef 中的ref 关键字传递参数只允许您从CalculateByRef 内部修改MainitemList 变量的值。

    根据您所展示的内容,在这种情况下,您似乎不需要 ref 关键字。

    HTH

    【讨论】:

      猜你喜欢
      • 2010-09-24
      • 2020-05-14
      • 2016-12-18
      • 1970-01-01
      • 1970-01-01
      • 2020-06-08
      • 1970-01-01
      • 2016-07-19
      • 2010-10-09
      相关资源
      最近更新 更多