在C#中,你在哪里使用参数前面的“ref”?

In C#, where do you use “ref” in front of a parameter?

关于"ref"和"out"参数的定义已经有很多问题了,但它们看起来设计不好。有没有你认为裁判是正确的解决方案?

似乎你总是可以做些更干净的事情。有人能给我一个例子,说明这是解决问题的"最佳"解决方案吗?


在我看来,ref在很大程度上弥补了宣布新的效用类型的困难,以及将信息"附加"到现有信息的困难,这些都是c从其诞生以来通过linq、generics和匿名类型朝着解决问题迈出的巨大一步。

所以不,我不认为有太多明确的使用案例。我认为这在很大程度上是语言最初设计方式的遗物。

我认为在需要从函数和返回值返回某种类型的错误代码的情况下(如上所述),这仍然是有意义的,但没有其他任何东西(因此更大的类型是不合理的)。如果我在项目中到处这样做,我可能会为Thing和Error定义一些通用的包装器类型。-代码,但在任何给定的情况下,refout都可以。


嗯,ref通常用于专门的案例,但我不会称之为冗余或C的遗留功能。例如,您会看到它(和out在xna中使用了很多。在XNA中,Matrixstruct和相当大的一个(我相信是64字节),通常最好是将它传递给使用ref的函数,以避免复制64字节,但只有4或8个字节。专业的C功能?当然。不再有太多的用途,还是设计不好?我不同意。


一个领域是使用小型实用程序功能,例如:

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

我看不到任何"更清洁"的替代品。当然,这并不完全是体系结构级别。


P/Invoke是我唯一能真正想到的地方,在这里你必须使用ref或out。其他情况下,他们可以方便,但如你所说,一般有另一种,更清洁的方式。


如果您想要返回多个对象,那么由于一些未知的原因,这些对象并没有绑定到一个对象中。

1
void GetXYZ( ref object x, ref object y, ref object z);

编辑:Divo建议使用out参数更适合这个。我得承认,他说得有道理。我将把这个答案留在这里,作为一个记录,这是一个不准确的解决方案。在这种情况下,胜于裁判。


ref的一种设计模式是双向访问。

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

1
2
3
4
5
6
7
public void Transfer(ref int value)
{
    if (Loading)
        value = ReadInt();
    else
        WriteInt(value);
}

对于其他原始类型也有类似的方法-boolstring等。

然后,在一个需要"可转移"的类上,您将编写如下的方法:

1
2
3
4
5
6
public void TransferViaStorage(Storage s)
{
    s.Transfer(ref _firstName);
    s.Transfer(ref _lastName);
    s.Transfer(ref _salary);
}

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

实际上,您只是列出了所有需要传输的字段,因此它非常接近声明性编程,而不是命令式编程。这意味着您不需要编写两个函数(一个用于读取,一个用于写入),并且考虑到我在这里使用的设计是顺序相关的,那么确定字段总是以相同的顺序读取/写入非常方便。

一般来说,当一个参数被标记为ref时,您不知道该方法是读还是写,这允许您设计在两个方向中的一个方向上工作的访问者类,以对称方式调用(即,使用访问的方法不需要知道访问者的方向模式)类正在操作)。

比较:属性+反射

为什么要这样做而不是将字段属性化并使用反射来自动实现等效的TransferViaStorage?因为有时反射速度很慢,足以成为瓶颈(但总是进行概要分析以确保这一点——这几乎从来都不是真的,而且属性更接近于声明性编程的理想)。


它的真正用途是在创建结构时。C中的结构是值类型,因此在值传递时总是完全复制。如果您需要通过引用传递它,例如出于性能原因或因为函数需要对变量进行更改,您将使用ref关键字。

我可以看到是否有人有一个具有100个值的结构(显然已经有问题了),您可能希望通过引用传递它来防止复制100个值。如果返回大型结构并重写旧值,则可能会出现性能问题。


我认为最好的用途是那些你通常看到的;你需要有一个值和一个"成功指示器",这不是一个函数的例外。


当您必须使用"ref"关键字时,有一个明显的例子。如果对象是定义的,但不是在要调用的方法的范围之外创建的,并且要调用的方法应该执行'new'来创建它,则必须使用'ref'。例如,{object a; Funct(a);} {Funct(object o) {o = new object; o.name ="dummy";}不会对object 'a'做任何事情,也不会在编译或运行时抱怨它。它什么都做不了。{object a; Funct(ref a);} {Funct(object ref o) {o = new object(); o.name ="dummy";}将使'a'成为新的对象,名称为"dummy。但如果'new'已经完成,则不需要参考(但如果提供,则有效)。{object a = new object(); Funct(a);} {Funct(object o) {o.name ="dummy";}


使用"ref"关键字的明显原因是当您希望通过引用传递变量时。例如,将值类型(如System.Int32)传递给方法并更改其实际值。更具体的用法可能是当您想交换两个变量时。

1
2
3
4
public void Swap(ref int a, ref int b)
{
   ...
}

使用"out"关键字的主要原因是从一个方法返回多个值。我个人更喜欢将值包装在一个专门的结构或类中,因为使用out参数会产生相当难看的代码。用"out"传递的参数就像用"ref"传递的参数。

1
2
3
4
public void DoMagic(out int a, out int b, out int c, out int d)
{
   ...
}