在C#中,什么时候需要在参数前面使用"ref"?

17

已经有很多问题讨论了"ref"和"out"参数的定义,但它们似乎都是糟糕的设计。你认为在哪些情况下ref是正确的解决方案呢?

看起来你总是可以使用更简洁的方法。能否给出一个例子,在这种情况下这将是最好的解决方案?

10个回答

11

在我看来,ref 在声明新的实用类型和“夹带信息”到现有信息的困难方面起到了很大的弥补作用。这些都是 C# 从创世以来通过 LINQ、泛型和匿名类型解决的重大问题。

因此,我认为现在并没有很多明显的使用案例。我认为它在很大程度上是语言最初设计时的遗留物。

我认为,仍然有一种情况是使用 ref 仍然有意义的(如上所述),即当您需要从函数返回某种错误代码以及返回值但不需要其他任何信息(因此一个更大的类型并不合适)时。如果在项目中频繁使用此操作,我可能会定义一些通用的包装类型来包含附加错误码的数据,但在任何给定的情况下,refout 都可以接受。


1
如果在一个项目中广泛使用“返回代码+值”的结构,我赞同制作包装类型的想法。 - Fredrik Mörk

9

嗯,ref通常用于特殊情况,但我不认为它是C#中多余的或遗留功能。例如,在XNA中,你会经常看到它(和out)的使用。在XNA中,Matrix是一个struct,而且相当庞大(我相信有64个字节),如果你使用ref将其传递给函数,可以避免复制64个字节,只需复制4或8个字节,这通常是最好的。一个专业的C#特性?当然是。不再有太多用处或表明设计不良吗?我不同意。


7

一个领域在于使用小型实用函数,例如:

void Swap<T>(ref T a, ref T b) { T tmp = a; a = b; b = tmp; }  

我在这里没有看到任何更好的替代方案。虽然这并不完全符合架构级别要求。

4

P/Invoke是我唯一能想到必须使用ref或out的地方。其他情况下,它们可能很方便,但像你所说的那样,通常有另一种更简洁的方法。


3
如果您想要返回多个对象,但由于某些未知原因它们没有被绑定为单个对象,该怎么办?
void GetXYZ( ref object x, ref object y, ref object z);

编辑:divo建议在这种情况下使用OUT参数会更合适。我必须承认,他说得对。我将保留这个答案,作为一份记录,但这是一个不充分的解决方案。在这种情况下,OUT优于REF。


我认为在大多数情况下,您可以重新设计代码,将类似的内容组合成更好的类型,或者以更清晰且仍然高效的方式单独返回每个内容。 - mqp
@mquander-完全同意。我认为如果你这样做,要么是你的方法可能做了太多的事情,要么是你的对象模型有问题。话虽如此,我相信有人有充分的理由这样做。 - John MacIntyre
4
在这种情况下,我更喜欢使用“输出参数(out parameters)”,因为意图更加明确。 - Dirk Vollmar
区别在于,“传递给 ref 参数的参数必须首先被初始化。这与 out 不同,out 的参数在传递之前不必明确初始化”(参见 http://msdn.microsoft.com/en-us/library/t3c3bfhx.aspx)。 - Dirk Vollmar

1

ref 有用的一个设计模式是双向访问者。

假设您有一个 Storage 类,可以用来加载或保存各种原始类型的值。它处于 Load 模式或 Save 模式。它有一组重载方法称为 Transfer,这里是处理 int 值的示例。

public void Transfer(ref int value)
{
    if (Loading)
        value = ReadInt();
    else
        WriteInt(value);
}

对于其他原始类型,例如boolstring等,也会有类似的方法。

然后,在需要“可传输”的类上,您可以编写如下方法:

public void TransferViaStorage(Storage s)
{
    s.Transfer(ref _firstName);
    s.Transfer(ref _lastName);
    s.Transfer(ref _salary);
}

这个单一的方法可以从Storage中加载字段,也可以将字段保存到Storage,具体取决于Storage对象所处的模式。

实际上,您只需列出需要传输的所有字段,因此它更接近声明式编程而不是命令式编程。这意味着您不需要编写两个函数(一个用于读取,一个用于写入),并且由于我在这里使用的设计是有序的,因此确切地知道字段将始终以相同的顺序读取/写入非常方便。

总的来说,当参数标记为ref时,您不知道方法是要读取它还是写入它,这使您能够设计访问者类,这些类可以朝两个方向之一工作,旨在以对称方式调用(即被访问的方法不需要知道访问者类正在操作的方向模式)。

比较:属性+反射

为什么要这样做,而不是将字段进行属性化,并使用反射自动实现等效的TransferViaStorage?因为有时候反射足够慢,成为瓶颈(但始终要进行配置文件以确保 - 它几乎从来不是真的,而属性更接近于声明式编程的理想)。


1

我认为最好的用法是那些通常看到的用法;你需要同时拥有一个值和一个“成功指示器”,而不是从函数中抛出异常。


3
但这不是 out 参数的完美应用场景吗?对我来说,相比 refout 更加清晰明了,而且在 BCL 中也更为常用(例如 <type>.TryParse 方法中)。 - Dirk Vollmar
太对了。我显然需要睡觉了,这里已经很晚了... ;) - Fredrik Mörk

1

这个的真正用途是在创建结构体时。在C#中,结构体是值类型,因此当按值传递时总是完全复制。如果您需要按引用传递它,例如出于性能原因或因为函数需要更改变量,则应使用ref关键字。

我可以看到如果某人有一个具有100个值的结构体(显然已经是一个问题),您可能希望通过引用传递它以防止复制100个值。那么返回该大型结构体并覆盖旧值可能会导致性能问题。


1
在引用类型的情况下,当你使用'ref'传递参数时,并不是完全冗余的,因为接收该变量的函数可以更改你的引用以指向一个完全不同的引用(或null)。如果你没有使用'ref',它只能对你的现有实例进行变异。 - mqp
1
这个信息是错误的!在C#中,默认情况下引用是按值传递的。请参阅http://www.yoda.arachsys.com/csharp/parameters.html。 - Dirk Vollmar
1
正如divo的链接所详细说明的那样,'ref'关键字确实是必需的。如果您不使用'ref'关键字,实际上您有两个对同一对象的单独引用。因此,对对象的更改将从函数外部可见,但对引用的更改(即将其置为空)则不会。 - mqp
1
结构体中的100个值的信息不正确。结构体(即使前面加上ref)仍然会被复制到堆栈顶部,以便被调用的方法执行其任务。ref关键字只是表示在被调用的方法完成其任务后应将结构体再次复制回来。在您的情况下,我建议使用引用类型对象(仅传递引用)。 - tofi9
@taoufik:你有关于通过ref传递的结构体被复制的参考资料吗?根据C#规范,以下内容是成立的:“用于引用参数的参数必须是一个变量,在方法执行期间,引用参数表示与参数变量相同的存储位置。”(参见http://download.microsoft.com/download/3/8/8/388e7205-bc10-4226-b2a8-75351c669b09/CSharp%20Language%20Specification.doc中的1.6.6.1参数) - Dirk Vollmar
显示剩余2条评论

0
使用“ref”关键字的明显原因是当您想要通过引用传递变量时。例如,将值类型(如System.Int32)传递给方法并更改其实际值。更具体的用法可能是当您想要交换两个变量时。
public void Swap(ref int a, ref int b)
{
   ...
}

使用“out”关键字的主要原因是从方法中返回多个值。个人而言,我更喜欢将这些值包装在一个专门的结构体或类中,因为使用“out”参数会产生相当丑陋的代码。使用“out”传递的参数 - 就像“ref”一样 - 是通过引用传递的。
public void DoMagic(out int a, out int b, out int c, out int d)
{
   ...
}

0

有一种情况必须使用'ref'关键字。如果对象在调用的方法的作用域之外已被定义但未被创建,且您想要调用的方法应该使用'new'来创建它,则必须使用'ref'。例如:{object a; Funct(a);} {Funct(object ref o) {o = new object(); o.name = "dummy";}将不会对对象'a'进行任何操作,也不会在编译或运行时报错。它只是不会执行任何操作。{object a; Funct(ref a);} {Funct(object ref o) {o = new object(); o.name = "dummy";}将导致'a'成为一个名为"dummy"的新对象。但是,如果'new'已经完成,则不需要使用'ref'(但如果提供了,则仍然有效)。{object a = new object(); Funct(a);} {Funct(object o) {o.name = "dummy";}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接