已经有很多问题讨论了"ref"和"out"参数的定义,但它们似乎都是糟糕的设计。你认为在哪些情况下ref是正确的解决方案呢?
看起来你总是可以使用更简洁的方法。能否给出一个例子,在这种情况下这将是最好的解决方案?
在我看来,ref
在声明新的实用类型和“夹带信息”到现有信息的困难方面起到了很大的弥补作用。这些都是 C# 从创世以来通过 LINQ、泛型和匿名类型解决的重大问题。
因此,我认为现在并没有很多明显的使用案例。我认为它在很大程度上是语言最初设计时的遗留物。
我认为,仍然有一种情况是使用 ref
仍然有意义的(如上所述),即当您需要从函数返回某种错误代码以及返回值但不需要其他任何信息(因此一个更大的类型并不合适)时。如果在项目中频繁使用此操作,我可能会定义一些通用的包装类型来包含附加错误码的数据,但在任何给定的情况下,ref
和 out
都可以接受。
嗯,ref
通常用于特殊情况,但我不认为它是C#中多余的或遗留功能。例如,在XNA中,你会经常看到它(和out
)的使用。在XNA中,Matrix
是一个struct
,而且相当庞大(我相信有64个字节),如果你使用ref
将其传递给函数,可以避免复制64个字节,只需复制4或8个字节,这通常是最好的。一个专业的C#特性?当然是。不再有太多用处或表明设计不良吗?我不同意。
一个领域在于使用小型实用函数,例如:
void Swap<T>(ref T a, ref T b) { T tmp = a; a = b; b = tmp; }
P/Invoke是我唯一能想到必须使用ref或out的地方。其他情况下,它们可能很方便,但像你所说的那样,通常有另一种更简洁的方法。
void GetXYZ( ref object x, ref object y, ref object z);
编辑:divo建议在这种情况下使用OUT参数会更合适。我必须承认,他说得对。我将保留这个答案,作为一份记录,但这是一个不充分的解决方案。在这种情况下,OUT优于REF。
ref
有用的一个设计模式是双向访问者。
假设您有一个 Storage
类,可以用来加载或保存各种原始类型的值。它处于 Load
模式或 Save
模式。它有一组重载方法称为 Transfer
,这里是处理 int
值的示例。
public void Transfer(ref int value)
{
if (Loading)
value = ReadInt();
else
WriteInt(value);
}
对于其他原始类型,例如bool
、string
等,也会有类似的方法。
然后,在需要“可传输”的类上,您可以编写如下方法:
public void TransferViaStorage(Storage s)
{
s.Transfer(ref _firstName);
s.Transfer(ref _lastName);
s.Transfer(ref _salary);
}
这个单一的方法可以从Storage
中加载字段,也可以将字段保存到Storage
,具体取决于Storage
对象所处的模式。
实际上,您只需列出需要传输的所有字段,因此它更接近声明式编程而不是命令式编程。这意味着您不需要编写两个函数(一个用于读取,一个用于写入),并且由于我在这里使用的设计是有序的,因此确切地知道字段将始终以相同的顺序读取/写入非常方便。
总的来说,当参数标记为ref
时,您不知道方法是要读取它还是写入它,这使您能够设计访问者类,这些类可以朝两个方向之一工作,旨在以对称方式调用(即被访问的方法不需要知道访问者类正在操作的方向模式)。
比较:属性+反射
为什么要这样做,而不是将字段进行属性化,并使用反射自动实现等效的TransferViaStorage
?因为有时候反射足够慢,成为瓶颈(但始终要进行配置文件以确保 - 它几乎从来不是真的,而属性更接近于声明式编程的理想)。
我认为最好的用法是那些通常看到的用法;你需要同时拥有一个值和一个“成功指示器”,而不是从函数中抛出异常。
out
参数的完美应用场景吗?对我来说,相比 ref
,out
更加清晰明了,而且在 BCL 中也更为常用(例如 <type>.TryParse
方法中)。 - Dirk Vollmar这个的真正用途是在创建结构体时。在C#中,结构体是值类型,因此当按值传递时总是完全复制。如果您需要按引用传递它,例如出于性能原因或因为函数需要更改变量,则应使用ref关键字。
我可以看到如果某人有一个具有100个值的结构体(显然已经是一个问题),您可能希望通过引用传递它以防止复制100个值。那么返回该大型结构体并覆盖旧值可能会导致性能问题。
public void Swap(ref int a, ref int b)
{
...
}
public void DoMagic(out int a, out int b, out int c, out int d)
{
...
}
有一种情况必须使用'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";}