为什么某些C#库函数不遵循“ ref”参数传递约定

时间:2018-10-03 02:45:19

标签: c# ref

有很多例子,让我们以数组复制方法为例。 Array.Copy is方法的签名如下

public static void Copy (Array sourceArray, long sourceIndex, Array destinationArray, long destinationIndex, long length);

仅从签名来看,不能说sourceArray不会更改,而destinationArray会更改,即使它像Int数组那样简单。 程序员对关键字“ ref”的保证在这里已经失去。

在我看来,最好将destinationArray参数标记为“ ref Array”。如果以此方式完成,语法将与关键字“ ref”的用法更加一致,表明被传入的对象可能会修改传入的对象,并且该更改对于调用者是可见的。我可以想到的关于简化关键字“ ref”的唯一好处是可以节省一些按键。还是只是在模仿C / C ++样式而没有太多思考。

我的问题是:这个设计决定背后有哪些调味料?

更新:为了便于记录,我提倡一个数组与其元素具有相同的值/引用类别,从而使Fun(array)和Fun(ref array)之间明确消失,这是程序员的相同保证。获得Fun(int)和Fun(ref int)。效率的优化可以留给实施级别。

3 个答案:

答案 0 :(得分:4)

数组是引用类型。您可以按值传递引用,它们引用的实例仍将与被修改的实例相同。被调用者正在使用其自己的引用来修改同一实例,而没有理由将其完全更改为完全不同的实例(这实际上是ref的使用位置。)

没有任何约定规定在传递引用类型时使用ref-通常不需要大部分时间,除非有提到,如果您的方法实际上打算像这样完全更改实例, :

class Foo { public int Value; }

public static void ReplaceFoo(ref Foo foo)
{
    foo = new Foo { Value = 2 };
}

var foo = new Foo { Value = 1 };
Console.WriteLine(foo.Value);
ReplaceFoo(ref foo);
Console.WriteLine(foo.Value);
  

仅从签名来看,不能说sourceArray不会更改,而destinationArray会更改

为什么这是个问题?没有人会仅在关注方法签名而忽略参数名称的情况下读取API。签名供编译器用来区分重载。凡是阅读Array.Copy()的API的人都会理解,sourceArray将保持不变,因为该方法从那里获取值,而destinationArray将被修改,成为接收该方法的对象。值-除非它们不会说英语(可以,但是大多数API用英语编写)。

我能想到的唯一的其他情况是读者是否会困惑,如果他们没有先验知识,即数组是.NET中的引用类型。但是在充其量的情况下误用ref并不能解决该问题。

答案 1 :(得分:0)

C#(和.NET)包括引用类型和值类型。

通常(缺少refout关键字)将参数传递给方法by value。因此,如果将整数传递给函数,则将传递整数值。如果在函数调用中放置一个引用数组的变量(请记住所有数组都是引用类型System.Array的实例),则该变量的值(即对该数组的引用)将传递给该函数。

因此,在函数内,代码将在该数组上播放。当函数返回时,该变量(在调用者的范围内)仍引用同一对象。但是,该函数可能对该数组进行了 mutt 修改,因此变量(在调用方作用域中)可能引用更改的对象。

如果通过引用传递值类型(使用ref关键字),则该函数可以更改参数的值,并且当函数返回时,该变量(在调用方作用域中)将收到新的值。

但是,如果您在引用类型的参数上使用ref(或out),则会通过引用传递引用。因此,例如,您可以传入五个整数的数组,并且该函数可以分配该参数和十个整数的数组(它们的类型相同,但对象肯定不同)。在调用方中,当函数返回时,与该参数关联的变量将在调用过程中看到其所指的内容完全更改。

在您的示例中,调用方将实例化两个具有相同类型和兼容长度的数组(如果源索引和目标索引为0并且长度为sourceArray.Length,则通常为相同长度)。该函数不会更改目标数组参数所指向的对象,而只是从源中填充目标。

实际上,如果目的地是by ref,它将不会那么灵活。考虑一个目标的长度为30个条目的情况,而您的意图是用源填充中间的十个数组条目。它只是工作。不会带有ref目标参数(无需进行更多工作)。

答案 2 :(得分:-1)

省略ref关键字的原因是,在大多数情况下,将其包括在内不会有任何区别,因此是多余的。但是,实际上在某些情况下确实有所不同。数组是引用类型,表示表示该引用的值已传递。通常,更新传入的值将触发对原始对象的更新。但是,如果您创建一个NEW数组并将传入的参数分配给新项目,则引用将丢失-而ref关键字将其保留。