哪个更好,返回值还是输出参数?

时间:2009-05-01 09:37:26

标签: c# reference

如果我们想从方法中获取值,我们可以使用返回值,如下所示:

public int GetValue(); 

或:

public void GetValue(out int x);

我真的不明白它们之间的区别,所以,不知道哪个更好。你能解释一下吗?

谢谢。

18 个答案:

答案 0 :(得分:150)

当方法没有返回任何其他内容时,返回值几乎始终是正确的选择。 (事实上​​,如果我有选择的话,我想不出任何曾经希望使用out参数的void方法的情况.C#7的Deconstruct语言支持的解构方法是这条规则非常非常罕见的例外。)

除了其他任何内容之外,它还会阻止调用者分别声明变量:

int foo;
GetValue(out foo);

VS

int foo = GetValue();

输出值也会阻止方法链接:

Console.WriteLine(GetValue().ToString("g"));

(实际上,这也是属性设置器的问题之一,这就是构建器模式使用返回构建器的方法的原因,例如myStringBuilder.Append(xxx).Append(yyy)。)

此外,out参数稍微难以用于反射,通常也会使测试更加困难。 (通常会花费更多精力来模拟返回值而不是输出参数)。基本上我没有想到他们让更容易 ......

返回值FTW。

编辑:就发生的事情而言......

基本上,当您为“out”参数传递参数时,传入变量。 (数组元素也被归类为变量。)您调用的方法在其堆栈上没有参数的“新”变量 - 它使用您的变量进行存储。变量中的任何更改都会立即可见。以下是显示差异的示例:

using System;

class Test
{
    static int value;

    static void ShowValue(string description)
    {
        Console.WriteLine(description + value);
    }

    static void Main()
    {
        Console.WriteLine("Return value test...");
        value = 5;
        value = ReturnValue();
        ShowValue("Value after ReturnValue(): ");

        value = 5;
        Console.WriteLine("Out parameter test...");
        OutParameter(out value);
        ShowValue("Value after OutParameter(): ");
    }

    static int ReturnValue()
    {
        ShowValue("ReturnValue (pre): ");
        int tmp = 10;
        ShowValue("ReturnValue (post): ");
        return tmp;
    }

    static void OutParameter(out int tmp)
    {
        ShowValue("OutParameter (pre): ");
        tmp = 10;
        ShowValue("OutParameter (post): ");
    }
}

结果:

Return value test...
ReturnValue (pre): 5
ReturnValue (post): 5
Value after ReturnValue(): 10
Out parameter test...
OutParameter (pre): 5
OutParameter (post): 10
Value after OutParameter(): 10

差异在于“后”步骤 - 即在局部变量或参数改变之后。在ReturnValue测试中,这与静态value变量没有区别。在OutParameter测试中,value变量由行tmp = 10;

更改

答案 1 :(得分:26)

什么更好,取决于您的具体情况。 out存在的一个原因是为了方便从一个方法调用中返回多个值:

public int ReturnMultiple(int input, out int output1, out int output2)
{
    output1 = input + 1;
    output2 = input + 2;

    return input;
}

因此,一个人的定义并不比另一个好。但通常你会想要使用一个简单的回报,除非你有上述情况。例如。

修改 这是一个示例,说明关键字存在的原因之一。上述内容绝不是最佳实践。

答案 2 :(得分:23)

您通常应该选择返回值而不是out param。如果你发现你自己编写需要做两件事的代码,那么params就是一种诡计。一个很好的例子是Try模式(例如Int32.TryParse)。

让我们考虑两种方法的调用者必须做什么。对于第一个例子,我可以写这个......

int foo = GetValue();

请注意,我可以声明一个变量并通过您的方法在一行中分配它。对于第二个例子,它看起来像这样......

int foo;
GetValue(out foo);

我现在被迫先声明我的变量并将我的代码写成两行。

<强>更新

在询问这些类型的问题时,一个好看的地方是.NET Framework设计指南。如果您有图书版本,那么您可以看到Anders Hejlsberg和其他人关于此主题的注释(第184-185页),但在线版本在这里......

http://msdn.microsoft.com/en-us/library/ms182131(VS.80).aspx

如果你发现自己需要从API返回两个东西,那么将它们包装在struct / class中会比out param更好。

答案 3 :(得分:11)

使用尚未提及的out参数有一个原因:调用方法有义务接收它。如果你的方法产生一个调用者不应该丢弃的值,那么使它成为out强制调用者专门接受它:

 Method1();  // Return values can be discard quite easily, even accidentally

 int  resultCode;
 Method2(out resultCode);  // Out params are a little harder to ignore

当然,调用者仍然可以忽略out param中的,但是你已经引起了他们的注意。

这是一种罕见的需求;更常见的情况是,你应该使用异常来解决真正的问题,或者返​​回一个带有“FYI”状态信息的对象,但是在某些情况下这很重要。

答案 4 :(得分:8)

主要是偏好

我更喜欢退货,如果您有多个退货,您可以将它们包装在结果DTO中

public class Result{
  public Person Person {get;set;}
  public int Sum {get;set;}
}

答案 5 :(得分:5)

您几乎应该总是使用返回值。 “out”参数会对许多API,组合等产生一些摩擦。

最值得注意的异常是想要返回多个值(.Net Framework在4.0之前没有元组),例如使用TryParse模式。

答案 6 :(得分:4)

您只能有一个返回值,而您可以有多个输出参数。

在这些情况下,您只需要考虑参数。

但是,如果您需要从方法中返回多个参数,您可能希望查看从OO方法返回的内容并考虑是否最好使用这些参数返回对象或结构。因此,您将再次返回返回值。

答案 7 :(得分:2)

我希望以下代替这个简单示例中的任何一个。

public int Value
{
    get;
    private set;
}

但是,它们都非常相似。通常,如果需要从方法中传回多个值,则只能使用“out”。如果要在方法中输入和输出值,可以选择“ref”。我的方法是最好的,如果你只返回一个值,但如果你想传递一个参数并获得一个值,你可能会选择你的第一选择。

答案 8 :(得分:1)

正如其他人所说的那样:回报价值,而不是出现参数。

我可以向您推荐“框架设计指南”(第2版)吗?第184-185页介绍了避免使用params的原因。整本书将引导您在各种.NET编码问题上朝着正确的方向前进。

与框架设计指南结盟是使用静态分析工具FxCop。您可以在Microsoft的网站上找到这个免费下载。在编译的代码上运行它,看看它说的是什么。如果它抱怨成百上千的事情......不要惊慌!平静而细致地看待每个案例的内容。不要急于尽快解决问题。了解它告诉你的是什么。你将被置于掌握的道路上。

答案 9 :(得分:1)

使用返回类型为bool的out关键字有时可以减少代码膨胀并提高可读性。 (主要是当out参数中的额外信息经常被忽略时。)例如:

var result = DoThing();
if (result.Success)
{
    result = DoOtherThing()
    if (result.Success)
    {
        result = DoFinalThing()
        if (result.Success)
        {
            success = true;
        }
    }
}

VS

var result;
if (DoThing(out result))
{
    if (DoOtherThing(out result))
    {
        if (DoFinalThing(out result))
        {
            success = true;
        }
    }
}

答案 10 :(得分:1)

此外,返回值与异步设计范例兼容。

你不能指定一个功能&#34; async&#34;如果它使用ref或out参数。

总之,返回值允许方法链接,更清晰的语法(通过消除调用者声明其他变量的必要性),并允许异步设计,而不需要在将来进行实质性修改。

答案 11 :(得分:1)

我认为有用的几个场景之一就是在使用非托管内存时,你想要明确表示“返回”值应该手动处理,而不是期望它被处理掉独自一人。

答案 12 :(得分:1)

没有真正的区别,out参数在C#中允许方法返回多于一个值,即全部。

然而,有一些细微的差别,但不是它们真的很重要:

使用out参数将强制您使用两行,如:

int n;
GetValue(n);

使用返回值时,您可以在一行中执行此操作:

int n = GetValue();

另一个区别(仅对值类型正确并且仅当C#没有内联函数时)是使用返回值必然会在函数返回时使用OUT参数复制该值而不一定这样做。< / p>

答案 13 :(得分:1)

它们都有不同的用途,编译器不会对它们进行相同的处理。如果您的方法需要返回一个值,那么您必须使用return。 Out用于您的方法需要返回多个值的位置。

如果使用return,则首先将数据写入方法堆栈,然后再写入调用方法。在out的情况下,它直接写入调用方法堆栈。不确定是否还有其他差异。

答案 14 :(得分:0)

当您尝试返回在方法中声明的对象时,

out会更有用。

实施例

public BookList Find(string key)
{
   BookList book; //BookList is a model class
   _books.TryGetValue(key, out book) //_books is a concurrent dictionary
                                     //TryGetValue gets an item with matching key and returns it into book.
   return book;
}

答案 15 :(得分:0)

返回值是您的方法返回的正常值。

如果 out 参数,well out和ref是C#的2个关键词,它们允许将变量作为引用传递。

ref out 之间的最大区别是, ref 应该在之前初始化, out 不要

答案 16 :(得分:0)

请避免使用 out 参数。

尽管它们在某些情况下很有意义(例如在实现 Try-Parse Pattern 时),但它们很难掌握。

您自己(除非您对概念非常有经验)和其他开发人员(使用您的 API 或可能继承您的代码)引入错误或副作用的机会非常高。

根据微软的质量规则CA1021

<块引用>

尽管返回值很常见并且被大量使用,但正确应用 out 和 ref 参数需要中级设计和编码技能。为一般受众设计的图书馆架构师不应期望用户掌握使用 out 或 ref 参数的工作。

因此,如果没有很好的理由,请不要使用 outref

另见:

答案 17 :(得分:-1)

我怀疑我不会针对这个问题进行研究,但是我是一个非常经验丰富的程序员,我希望一些比较胸襟开阔的读者能够关注这个问题。

我相信它更适合确定性和纯净的价值返回过程(VRP),从而更适合面向对象的编程语言。

“ VRP”是函数的现代学术名称,该函数被称为表达式的一部分,并且具有返回值,该返回值在表达式的求值期间名义上替代了调用。例如。在诸如x = 1 + f(y)之类的语句中,功能f充当VRP。

“确定性”是指函数的结果仅取决于其参数的值。如果再次使用相同的参数值调用它,肯定会得到相同的结果。

“纯”表示没有副作用:调用该函数不会执行除计算结果以外的任何操作。实际上,这可以解释为没有重要副作用,因此,例如,如果VRP每次调用时都会输出调试消息,则可以忽略该消息。

因此,如果在C#中您的函数不是确定性的和纯函数,我想说您应该使其成为void函数(换句话说,不是VRP),并且需要返回的任何值都应为通过outref参数返回。

例如,如果您具有删除数据库表中某些行的功能,并且希望它返回已删除的行数,则应这样声明:

public void DeleteBasketItems(BasketItemCategory category, out int count);

如果您有时想调用此函数但不获取count,则可以随时声明重载。

您可能想知道为什么这种样式更适合面向对象的编程。广义上讲,它适合于可以(有点不精确)称为“过程编程”的编程风格,并且是一种更适合面向对象编程的过程编程风格。

为什么?对象的经典模型是它们具有属性(即属性),您可以通过读取和更新这些属性来查询和操纵对象(主要是)。程序性编程风格往往使执行此操作更加容易,因为您可以在获取和设置属性的操作之间执行任意代码。

过程式编程的缺点是,因为您可以在各处执行任意代码,所以可以通过全局变量和副作用获得一些非常钝性和易受漏洞影响的交互。

因此,很简单,对那些阅读您的代码的人来说, signal 是一个好习惯,因为该函数可能会由于使其不返回值而产生副作用。