17

关于“ref”和“out”参数的定义已经存在许多问题,但它们似乎是糟糕的设计。是否存在您认为 ref 是正确解决方案的情况?

似乎您总是可以做其他更清洁的事情。有人可以给我一个例子,说明这将是解决问题的“最佳”解决方案吗?

4

10 回答 10

11

在我看来,这在ref很大程度上弥补了声明新实用程序类型的困难以及将信息“附加”到现有信息的困难,这是 C# 自通过 LINQ、泛型和匿名类型诞生以来已采取巨大步骤解决的问题。

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

我确实认为在您需要从函数返回某种错误代码以及返回值的情况下它仍然是有意义的(如上所述),但没有别的(所以更大的类型并不是真正合理的。 ) 如果我在一个项目的所有地方都这样做,我可能会为 thing-plus-error-code 定义一些通用的包装器类型,但在任​​何给定的实例refout都可以。

于 2009-04-29T20:52:22.007 回答
9

好吧,ref通常用于特殊情况,但我不会将其称为冗余或 C# 的遗留功能。例如,您会看到它(和out)在 XNA 中被大量使用。在 XNA 中,aMatrix是一个struct并且相当大的一个(我相信 64 字节),通常最好将它传递给函数使用ref以避免复制 64 字节,但只有 4 或 8 个。一个专业的 C# 功能?当然。没有太多用处或表明设计不佳?我不同意。

于 2009-04-29T22:10:54.953 回答
7

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

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

我在这里看不到任何“更清洁”的选择。当然,这不完全是架构级别。

于 2009-04-29T21:21:00.283 回答
4

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

于 2009-04-29T20:58:03.677 回答
3

如果您想返回多个对象怎么办,由于某种未知的原因没有将它们绑定到一个对象中。

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

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

于 2009-04-29T20:56:16.070 回答
1

我认为最好的用途是您通常看到的用途;您需要同时拥有一个值和一个“成功指标”,这不是函数的例外。

于 2009-04-29T20:52:19.293 回答
1

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

我可以查看是否有人有一个具有 100 个值的结构(显然已经存在问题),您可能希望通过引用传递它以防止 100 个值被复制。那并返回那个大结构并覆盖旧值可能会出现性能问题。

于 2009-04-29T20:54:47.023 回答
1

一种有用的设计模式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?因为有时反射慢到足以成为瓶颈(但总是要分析以确保这一点 - 这几乎不是真的,并且属性更接近声明式编程的理想)。

于 2009-04-29T22:42:57.623 回答
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)
{
   ...
}
于 2009-04-29T21:52:22.673 回答
0

当您必须使用“ref”关键字时,有一种明显的情况。如果对象已定义但未在您打算调用的方法范围之外创建,并且您要调用的方法应该执行'new'创建它,则必须使用'ref'. eg{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'已经完成,则不需要 ref (但如果提供,则可以使用)。 {object a = new object(); Funct(a);} {Funct(object o) {o.name = "dummy";}

于 2012-04-23T20:16:01.240 回答