C#中使用"out"关键字的最佳实践

23

我正在尝试规范化在一个C#项目中使用"out"关键字的用法,特别是对于任何公共方法。我似乎找不到任何最佳实践,并想知道什么是好的或不好的。

有时我会看到一些方法签名看起来像这样:

public decimal CalcSomething(Date start, Date end, out int someOtherNumber){}

目前为止,我只是有一种感觉,这让我感到不舒服。出于某些原因,我更愿意看到:

public Result CalcSomething(Date start, Date end){}

结果是包含十进制数和someOtherNumber的类型。我认为这样更易于阅读,因为它允许扩展Result或添加属性而不会破坏代码。这也意味着调用此方法的用户在调用之前不必声明一个本地作用域的"someOtherNumber"。从使用期望来看,不是所有的调用者都会对"someOtherNumber"感兴趣。

相比之下,我现在能想到的在.NET框架中唯一适用“out”参数的实例是尝试解析(TryParse())之类的方法。这些实际上使调用者编写更简单的代码,因为调用者主要关心的是out参数。

int i;
if(int.TryParse("1", i)){
  DoSomething(i);
}

我认为只有在返回类型为bool且期望的用法是"out"参数始终对调用者感兴趣时,才应使用"out"。

你怎么看?


3
在兴趣范畴之外(现在就射我吧),在这个例子中,一些其他数字会出现在someOtherNumber中? - Rob
1
我永远不会仅使用out来输出布尔值;这感觉很奇怪。我宁愿返回布尔值并将out用于更大的值(就像您的TryParse示例中一样)。 - Pierre Arnaud
彼埃尔 - 我怀疑 OP 的意思是,如果你有像 TryParse 示例那样返回 bool 值而不是作为 out 参数,那么你可以使用 out 参数。 - Ant
10个回答

23

在使用out参数时,静态代码分析(=FxCop)规则中有一个指向你的原因。我会说:只有在互操作类型场景中真正需要时才使用out。在所有其他情况下,不要使用out。但也许这只是我的看法?


1
刚发现这个链接:http://msdn.microsoft.com/en-us/library/ms182131.aspx,CA1021: AvoidOutParameters。FxCop规则……对我来说已经足够好了。谢谢。 - user1010
请注意,此规则明确提到只有受保护和公共方法才是问题。那么在内部方法中使用 out 呢? - heltonbiker
我的答案对于私有/内部方法是相同的:仅在交互类型场景中绝对需要时才使用输出参数。 - peSHIr

19
这是.NET Framework开发人员指南对于输出参数的说明:

避免使用out或引用参数。

使用定义了out或引用参数的成员需要开发人员理解指针、值类型和引用类型之间的微妙差别,以及out和引用参数之间初始化方面的差异。

但如果确实要使用它们:

将所有的输出参数放在所有按值传递和ref参数(不包括参数数组)之后,即使这导致了重载之间参数顺序的不一致也要如此操作。

这种约定使方法签名更易于理解。


26
开发者指南基本上是在说,你不应该使用输出参数,因为大多数开发人员太蠢了,无法理解自己选择的语言中非常基础的一个方面! - Stephen Martin
1
史蒂芬,这是非常正确的。我认为在需要它们的地方使用它们。 - Lamar
1
作为一名Java开发人员,每当我看到某个函数修改其输入之一时,通常都是在设计不良的类中。我猜"out"是一个为几个特定情况创建的关键字的例子。 - U Avalos

6

你的方法比之前更好,因为你可以通过“链式调用”来实现:

DoSomethingElse(DoThing(a,b).Result);

与...相对应
DoThing(a, out b);
DoSomethingElse(b);

在我看来,使用“out”实现的TryParse方法是一个错误。在链式操作中,它们本来可以非常方便。


3
你需要一种方法来判断解析是否成功。 - lacop
3
使用链式编程时,请使用 int.Parse() 或传递未解析的字符串。我不知道如何在不使用 out 参数的情况下获得 TryParse() 功能。 - gilly3

5

只有极少数情况下我会使用out。其中之一是,在面向对象的角度来看,如果您的方法返回两个变量不属于同一个对象。

例如,如果您想要获取文本字符串中最常见的单词和第42个单词,则可以在同一个方法中计算它们(只需要解析文本一次)。但对于您的应用程序,这些信息彼此没有关系:您需要最常见的单词用于统计目的,但您仅需要第42个单词,因为您的客户是迷恋道格拉斯·亚当斯的极客。

是的,这个例子非常牵强,但我没有更好的例子...


即使如此,我仍然会使用类/结构体(或从4.0开始的元组实例)。像虚构的“MostCommonAnd42thWordResult”这样的东西,但仍然。或者当然可以设计整个逻辑不同,以解决此示例的“矛盾性”和“不属于一个对象的感觉”。;-) - peSHIr

4

我必须补充一点,从C# 7开始,结合内联变量声明,使用out关键字在某些情况下可以生成非常易读的代码。尽管通常应当返回一个(命名)元组,但当方法具有布尔结果时,例如:

if (int.TryParse(mightBeCount, out var count)
{
    // Successfully parsed count
}

我还应该提到,为那些元组有意义的情况定义一个特定的类往往更合适。这取决于有多少返回值以及你如何使用它们。我建议,当超过3个时,无论如何都将它们放入一个类中。


2

避免使用out。它只是作为低级别的便利存在。但在高级别上,它是一种反技巧。

int? i = Util.TryParseInt32("1");
if(i == null)
    return;
DoSomething(i);

1
为什么被投票了?试过编译吗?TryParse() 方法返回一个布尔值,用于确定解析是否成功,以避免使用异常进行程序控制 - 这样做很好。 - Oliver Friedrich
2
@Justice:不太确定您在举的例子中想表达什么意思。在.NET Framework中,TryParse()的实现并非如此。 - Mitch Wheat
1
我认为这是一个应该如何实现的示例。+1 - Eyvind
将 int.TryParse 更改为 Util.TryParseInt32。 - yfeldblum
1
@Eyvind,这绝对是我希望int.TryParse实现的方式。 - yfeldblum

1

out的一个优点是编译器会验证CalcSomething确实将值分配给someOtherNumber。但它不会验证结果的someOtherNumber字段是否有值。


1
我的做法是通过Result构造函数强制初始化someOtherNumber字段。 - user1010
2
请注意 - 只有C#编译器才能做到这一点。您可以在其他.NET语言中公然违反此协议。 - plinth

1

如果您曾经看过并使用过MS命名空间System.Web.Security

MembershipProvider 
   public abstract MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status);

你需要一个桶。这是一个违反许多设计范例的类的示例。糟糕透了!

仅仅因为语言有输出参数并不意味着它们应该被使用,例如goto。

使用out更像是开发人员要么懒得创建类型,要么想尝试一种语言特性。 即使是完全牵强的上面的MostCommonAnd42ndWord示例,我也会使用List或一个新的类型contrivedresult,其中包含2个属性。

我在上面的解释中看到的唯一好的理由是在强制进行交互操作时。假设这是有效的陈述。


0

你可以创建一个通用的元组类来返回多个值。这似乎是一个不错的解决方案,但我无法避免地感觉到通过返回这样一个通用类型(Result在这方面也不好)会失去一些可读性。

然而,詹姆斯·柯兰也指出了一个重要的点,那就是编译器强制执行值的赋值。这是我在C#中看到的一般模式,你必须明确声明某些事情,以获得更可读的代码。另一个例子是override关键字,在Java中没有这个关键字。


0

如果你的结果比单个值更复杂,如果可能的话,你应该创建一个结果对象。我为什么要这样说呢?

  1. 整个结果被封装起来了。也就是说,你有一个单一的包裹,它告诉代码 CalcSomething 的完整结果。而不是让外部代码解释十进制返回值的含义,你可以为以前的返回值、你的 someOtherNumber 值等命名属性。

  2. 你可以包括更复杂的成功指标。你编写的函数调用可能会在开始之前结束,但异常抛出是报告错误的唯一方式。使用结果对象,你可以包括一个布尔或枚举的“成功”值,并进行适当的错误报告。

  3. 你可以延迟执行结果,直到你实际检查“结果”字段。也就是说,任何计算的执行都不需要在你使用值之前完成。


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