是通过引用传递的,只是一种遗产
本文关键字:遗产 一种 引用 | 更新日期: 2023-09-27 18:11:41
也许这对我来说有点天真,但我似乎真的找不到/想到"通过引用传递"的合适用例。更改不可变字符串(如其他Q/as所提到的)通常是可以避免的,返回多个变量通常通过返回Tuple, List, array等来更好地处理。
在我看来,MSDN上的例子很糟糕;我将简单地在Square
方法中返回一个值,而不是将其声明为void
。
在我看来,它有点像c#的遗留部分,而不是c#不可或缺的一部分。有没有比我更聪明的人试着解释一下为什么它仍然存在和/或一些实际的实际用例(即在几乎所有情况下都可以避免更改不可变字符串)。
注::我跟进了@KallDrexx和@newacct的一些评论现在我明白了,他们是对的,我错了:我的回答有些误导人。Scott Stanchfield的一篇很棒的文章"Java是按值传递,该死!"(Java特定,但仍然主要与c#相关)最终说服了我。
我将把我的回答中令人误解的部分去掉现在,但以后可能会删除它们。
不只是用于
ref
或out
参数。更重要的是,所有引用类型都是通过引用传递的(因此它们的名称),尽管这是透明的。这里有三个频繁的引用传递用例:
-
在传递时防止复制大的
struct
。假设您有一个byte[]
数组,表示一个二进制大对象(BLOB),可能有几兆字节大小的struct
类型,包含许多字段。这种类型的值可能会占用相当多的内存。现在你想把这个值传递给某个方法。你真的想按值传递它吗,比如创建一个临时副本?可以通过传递
ref
引用来避免不必要的大结构体复制。(幸运的是,像byte[]
这样的数组是引用类型,所以数组的内容已经通过引用传递了)通常建议(例如在Microsoft的框架设计指南中),如果具有值-类型语义的类型超过一定的大小(32字节),则应该将其实现为引用类型,因此这种用例不应该非常频繁。
-
可变性。如果您希望一个方法能够改变传递给它的
struct
值,并且您希望调用者观察到该对象的他的版本的变化,那么您需要通过引用传递(ref
)。如果该值按值传递给该方法,它将收到一个副本;修改副本将使原始对象保持不变。这一点在上面链接到的框架设计指南文章中也提到了。
注意对可变值类型的普遍建议(参见:"为什么可变结构是邪恶的?")。您应该很少使用
ref
或out
参数和值类型。 -
COM互操作通常需要声明
ref
和out
参数。
假设您希望有一个函数来改变一个值(注意,值,而不是对象),并且您还希望该函数返回一些成功指示符。一个好的做法是返回一个指示成功/失败的布尔值,但是值呢?所以你使用ref
:
bool Mutate(ref int val)
{
if(val > 0)
{
val = val * 2;
return true;
}
return false;
}
这是真的,在c#中通常有ref
和out
的替代品-例如,如果你想向调用者返回多个值,你可以返回一个元组,或自定义类型,或接收引用类型作为参数并更改其中的多个值。
然而,这些关键字仍然可以是一个方便的解决方案,在诸如互操作:
// C
int DoSomething(int input, int *output);
// C#
[DllImport(...)]
static extern int DoSomething(int input, ref int output);
从函数的角度来看,只有少数情况下显式ref参数是有用的。
我见过的一个例子:
public static void Swap<T>(ref T a, ref T b)
{
var temp = a;
a = b;
b = temp;
}
这种方法在某些排序算法中很有用,例如
有时使用ref形参的一个原因是作为一种优化技术。大型结构体(值类型)有时通过ref传递,即使被调用的方法无意修改结构体的值,以避免复制结构体的内容。您可能会认为,大型结构体最好是类(即引用类型),但是当您保持此类对象的大型数组时(例如在图形处理中),这有缺点。作为一种优化,这种技术并不能提高代码的可读性,但在某些情况下可以提高性能。
关于指针也可以问类似的问题。c#通过unsafe
和fixed
关键字支持指针。从函数的角度来看,它们几乎是不需要的:我真的想不出一个不使用指针就不能编码的函数。但是,指针并不是用来使语言更具表现力或使代码更具可读性的,它们被用作一种低级优化技术。
实际上,通过引用传递结构体确实是传递指向该结构体数据的指针的一种安全方式,这是大型结构体的一种典型的低级优化技术。
支持低级优化的特性是遗留特性吗?这可能取决于您的观点,但您并不是唯一的c#用户。他们中的许多人会告诉你,指针和其他低级结构的可用性是c#相对于其他语言的一大优势。我不认为ref参数是一个遗留特性,它们是语言的一个组成部分,在某些情况下很有用。
这并不意味着你必须使用ref参数,就像你不必使用LINQ、async/await或任何其他语言特性一样。当你需要它的时候,它就在那里。