【发布时间】:2008-09-25 15:38:37
【问题描述】:
为什么 .NET 中的“out”参数是个坏主意?
我最近被问到这个问题,但我没有真正的答案,除了它只是不必要地使应用程序复杂化。还有什么其他原因?
【问题讨论】:
标签: .net
为什么 .NET 中的“out”参数是个坏主意?
我最近被问到这个问题,但我没有真正的答案,除了它只是不必要地使应用程序复杂化。还有什么其他原因?
【问题讨论】:
标签: .net
嗯,我认为他们不是一个坏主意。 Dictionary<K, V> 有一个 TryGetValue 方法,这是一个很好的例子,为什么输出参数有时是一件非常好的事情。
当然,您不应过度使用此功能,但根据定义,这不是一个坏主意。尤其是在 C# 中,您必须在函数声明 and 调用中写下 out 关键字,这使得发生的事情一目了然。
【讨论】:
ref 或out 传递它。这就是这些修饰符的真正意义所在!是的,我同意这严重影响了功能范式。但有时它仍然有一个优点。
如果您关心通过接受不变性和消除副作用来编写可靠的代码,那么输出参数绝对是一个糟糕的主意。它迫使您创建可变变量来处理它们。 (并不是说 C# 无论如何都支持只读的方法级变量(至少在我使用的版本中,3.5)。
其次,它们通过强制开发人员设置和处理接收输出值的变量来减少函数的组合性。这是令人讨厌的仪式。您不能随便使用它们轻松地编写表达式。因此,调用这些函数的代码很快就会变成一大堆命令式的混乱,从而提供了很多隐藏错误的地方。
【讨论】:
out 参数在一些特殊情况下很有用,正是因为它们阻止了函数组合:例如,当一个函数可能返回一个 null 值,而你想要强制调用者在函数结果传递给下一个函数之前显式检查null。
我会说你给出的答案是最好的。如果可能,您应该始终远离out 参数进行设计,因为这通常会使代码变得不必要地复杂。
但这几乎适用于任何模式。如果不需要,请不要使用它。
【讨论】:
Out - 我认为参数是个坏主意。它们增加了副作用的风险并且难以调试。唯一好的解决方案是函数结果,问题是:对于函数结果,您必须为每个复杂结果创建一个元组或一个新类型。现在它在 c# 中应该相当容易,使用匿名类型和泛型。
顺便说一句:我也讨厌副作用。
【讨论】:
问题的措辞是of the "Have you stopped hitting your wife?" variety——它假定out 参数一定是个坏主意。在某些情况下 out 参数可能会被滥用,但是……
它们实际上非常适合 C# 语言。它们类似于 ref 参数,只是方法保证为它赋值并且调用者不需要初始化变量。
函数的正常返回值被推送到调用和退出函数的堆栈中。当你提供一个 out 参数时,你给方法一个特定的内存地址来保存结果。这也允许多个返回值,尽管使用结构通常更有意义。
它们非常适合 TryParse 模式,还为其他事物提供元数据,例如 SQL-CLR 的情况,其中 out 参数映射到 out 参数存储过程签名。
【讨论】:
嗯,这个没有明确的答案,但是 out 参数的简单用例是Int.TryParse("1",out myInt)
XXX.TryParse 方法的工作是,它将某种类型的值转换为另一种类型,它会返回一个布尔标志来指示转换成功或失败,其中一部分是转换后的值,另一部分是转换后的值,由该方法中的 out 参数携带。
此 TryParse 方法是在 .NET 2.0 中引入的,以克服以下事实:如果转换失败,XXX.Parse 将引发异常,并且您必须在语句周围添加 try/catch 才能捕获它。
所以基本上这取决于你的方法在做什么,以及方法调用者期望什么,如果你正在做一个返回某种形式的响应代码的方法,那么out参数可以用来执行返回的方法结果。
无论如何,微软在他们的design guideline (MSDN page)
中说“避免使用 out 参数”【讨论】:
我不认为他们是。滥用它们是一个坏主意,但这适用于任何编码实践/技术。
【讨论】:
我必须同意 GateKiller 的观点。如果微软将它们用作基础库的一部分,我无法想象参数会太糟糕。但是,与所有事物一样,最好适度。
【讨论】:
“out”很棒!
clear 声明参数包含方法返回的值。
编译器还会强制你初始化它(如果我们使用它作为返回参数,它应该被初始化)。
【讨论】:
使用 out 参数并不总是一个坏主意。通常对于尝试基于某种形式的输入创建对象的代码,最好提供一个没有参数和布尔返回值的 Try 方法,而不是强制方法使用者包装 try catch到处都是块,更不用说更好的性能了。 示例:
bool TryGetSomeValue(out SomeValue value, [...]);
在这种情况下,输出参数是个好主意。
另一种情况是您希望避免在方法之间传递代价高昂的大型结构。 例如:
void CreateNullProjectionMatrix(out Matrix matrix);
这个版本避免了昂贵的结构复制。
但是,除非出于特定原因需要 out,否则应该避免。
【讨论】:
out 根本没有任何论据。
它们只是稍微破坏了方法/函数的语义。一个方法通常应该接受一堆东西,然后吐出一个东西。至少,这就是大多数程序员的想法(我认为)。
【讨论】:
他们是个好主意。因为有时您只想从一个方法返回多个变量,而您不想为此创建一个“沉重”的类结构。包装类结构可以隐藏信息流动的方式。
我用这个来:
【讨论】:
out 变量无论如何都不错,如果我们需要从一个函数返回多个(特别是 2 个)变量,它确实是一个很酷的东西。有时,仅仅为了返回 2 个变量而创建自定义对象真的很乏味,out 是最终的解决方案。
【讨论】:
我想说你的答案是正确的;它通常是不必要的复杂性,通常有一个更简单的设计。您在 .NET 中使用的大多数方法都不会改变它们的输入参数,因此使用这种语法的一次性方法有点令人困惑。代码变得不那么直观了,而且在记录不完整的方法的情况下,我们无法知道该方法对输入参数做了什么。
此外,没有参数的方法签名将触发代码分析/FxCop 违规,如果这是您关心的指标。在大多数情况下,有一种更好的方法来实现使用“out”参数的方法的意图,并且可以重构该方法以返回有趣的信息。
【讨论】:
我会说我避免它的原因是因为让一个方法处理数据并返回一个逻辑数据是最简单的。如果它需要返回更多数据,它可能是进一步抽象或形式化返回值的候选者。
一个例外是如果返回的数据有点三级,比如可以为空的布尔值。它可以是真/假或未定义。 out 参数可以帮助解决其他逻辑类型的类似想法。
我有时使用的另一种方法是当您需要通过不允许使用“更好”方法的接口获取信息时。例如使用 .Net 数据访问库和 ORM。当您需要返回更新的对象图(在 UPDATE 之后)或新的 RDBMS 生成的标识符 (INSERT) 以及受语句影响的行数时。 SQL 在一条语句中返回所有这些,因此对方法的多次调用将不起作用,因为您的数据层、库或 ORM 仅调用一个通用 INSERT/UPDATE 命令,并且无法存储值以供以后检索。
从不使用动态参数列表或输出参数等理想做法并不总是可行的。同样,如果输出参数是真正正确的方法,请三思而后行。如果是这样,那就去吧。 (但一定要好好记录!)
【讨论】:
这就像 Tanqueray 人喜欢说的那样——一切都要适度。
我肯定会强调不要过度使用,因为它会导致“在 c# 中编写 c”,就像在 Python 中编写 java 一样(你不会接受使新语言特别的模式和习语,而是简单地用旧语言思考并转换为新语法)。不过,与往常一样,如果它有助于您的代码更优雅、更有意义,那就摇摆不定。
【讨论】:
我认为在某些情况下它们是一个好主意,因为它们允许从一个函数返回多个对象,例如:
DateTime NewDate = new DateTime();
if (DateTime.TryParse(UserInput, out NewDate) == false) {
NewDate = DateTime.Now;
}
非常有用:)
【讨论】:
我没有看到提到的一点是 out 关键字还要求调用者指定 out。这一点很重要,因为它有助于确保调用者明白调用这个函数会修改他的变量。
这就是我从不喜欢在 C++ 中使用引用作为输出参数的原因之一。调用者可以在不知道自己的参数会被修改的情况下轻松调用方法。
【讨论】:
out 关键字是必需的(请参阅SortedDictionart.TryGetValue)。它意味着通过引用传递,并告诉编译器以下内容:
int value;
some_function (out value);
没问题,some_function 没有使用通常是错误的未初始化值。
【讨论】:
value = some_function() 在这种特殊情况下会更有意义,但这不是重点。重点是表明编译器不会抱怨在函数调用中使用了未初始化的值。
我认为没有真正的原因,尽管我没有看到它经常使用(P/Invoke 调用除外)。
【讨论】:
.NET 之外的某些语言将它们用作无操作宏,仅向程序员提供有关如何在函数中使用参数的信息。
【讨论】:
我认为主要原因是“......虽然返回值很常见并且被大量使用,但out 和 ref参数的正确应用需要中间设计和编码技能。为普通观众设计的图书馆架构师不应该期望用户在没有 out 或 ref 参数的情况下掌握工作。”
请阅读更多内容:http://msdn.microsoft.com/en-us/library/ms182131.aspx
【讨论】: