返回值或out参数哪个更好?


147

如果要从方法获取值,则可以使用两个返回值之一,如下所示:

public int GetValue(); 

要么:

public void GetValue(out int x);

我不太了解它们之间的区别,因此,不知道哪个更好。你能解释一下吗?

谢谢。


3
我希望C#具有多个返回值,例如Python。
陷阱

12
@陷阱Tuple如果需要,可以返回a ,但是通常的共识是,如果需要返回多个事物,则这些事物通常以某种方式相关联,并且该关系通常最好表示为类。
法拉普

2
当前形式的C#中的@Pharap Tuples很难看,但这只是我的看法。另一方面,从可用性和生产率的角度来看,“一般共识”毫无意义。出于相同的原因,您不会创建用于返回几个值的类,而不会创建用于返回几个值作为ref / out参数的类。
陷阱2016年

@陷阱return Tuple.Create(x, y, z);不是那么丑。此外,将它们引入语言水平还为时过晚。我之所以不会创建一个从ref / out参数返回值的类的原因是因为ref / out参数仅对大型可变结构(如矩阵)或可选值才有意义,而后者则值得商bat。
法拉普

@Pharap C#团队正在积极寻求在语言级别引入元组。值得欢迎的是,.NET中的所有选项现在不胜枚举-匿名类型,.NET Tuple<>和C#元组!我只是希望C#允许从具有推断类型的编译器的方法中返回匿名类型(例如autoDlang)。
nawfal

Answers:


153

当方法没有其他要返回的值时,返回值几乎总是正确的选择。(事实上,我不能想到这里我想任何情况下,曾经想要一个空白方法与out参数,如果我当初的选择。C#7的Deconstruct方法语言支持的解构作为一个非常,非常罕见的例外)

除了其他方面,它使调用者不必单独声明变量:

int foo;
GetValue(out foo);

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;


2
您让我相信回报价值要好得多:)。但是我仍然想知道“深入”会发生什么。我的意思是,返回值和out参数在创建,分配和返回方式上是否有所不同?
Quan Mai

1
TryParse是使用out参数合适且干净时的最佳示例。尽管我在特殊情况下使用过它,例如if(WorkSucceeded(out List <string> errors),它与TryParse的模式基本相同
乍得·格兰特2009年

2
穷人无济于事。如对此问题的评论中所述,out参数具有几个有用的优点。优先选择收益还是收益取决于情况。
aaronsnoswell

2
@aaronsnoswell:哪些确切的评论?记住,的例子Dictionary.TryGetValue适用在这里,因为这不是一个空洞的方法。您能解释为什么要使用out参数而不是返回值吗?(即使是TryGetValue,我个人更喜欢返回值,其中包含所有输出信息。有关ParseResult<T>我如何设计它的示例,请参见NodaTime 。)
Jon Skeet 2012年

2
@吉姆:我想我们必须同意不同意。当我看到关于锤击人在头用这个你的观点,这也使得它对于那些谁不那么友好知道他们在做什么。关于异常而不是返回值的好处是,您不能轻易地忽略它并像没有发生任何事情一样进行下去……而对于返回值和out参数,您都不能对值做任何事情。
乔恩·斯基特

26

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

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

    return input;
}

因此,从定义上讲,一个并不比另一个更好。但是通常除非您遇到上述情况,否则通常要使用简单的收益。

编辑: 这是一个示例,说明关键字存在的原因之一。以上绝不是最佳实践。


2
我不同意这一点,为什么不返回带有4个整数的数据结构?这真是令人困惑。
乍得·格兰特

1
显然,有更多(更好)的方法可以返回多个值,我只是给OP一个理由,说明为什么out存在于第一位。
焦积云

2
我同意@Cloud。仅仅因为它不是最佳方法,并不意味着它不应该存在。
大脑2009年

好吧,该代码甚至无法编译一个……并且至少应该提到它被认为是不好的做法/不是首选。
乍得格兰特

1
它不会编译吗?那为什么呢?该示例可以完美编译。而且,请给出一个示例,说明为什么存在特定的语法/关键字,而不是最佳实践课程。
焦积云

23

通常,您应该更喜欢返回值而不是out参数。如果您发现自己编写的代码需要做两件事,那么输出参数是必不可少的。一个很好的例子是Try模式(例如Int32.TryParse)。

让我们考虑一下这两个方法的调用者将要做的事情。对于第一个示例,我可以这样写...

int foo = GetValue();

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

int foo;
GetValue(out foo);

我现在被迫提前声明我的变量,并用两行代码编写代码。

更新

提出这些类型的问题时,一个不错的地方是.NET Framework设计指南。如果您有书籍版本,则可以看到Anders Hejlsberg和其他人对此主题的注释(第184-185页),但在线版本在这里...

http://msdn.microsoft.com/zh-CN/library/ms182131(VS.80).aspx

如果您发现自己需要从API返回两件事,那么将它们包装在struct / class中将比out参数更好。


很好的答案,特别是对TryParse的引用,TryParse是一个(常见的)函数,它迫使开发人员使用(不常见的)输出变量。
2009年

12

使用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

当然,呼叫者仍然可以忽略outPARAM,但你叫他们的注意。

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


8

主要是偏爱

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

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

5

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

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

但是,如果您需要从方法中返回多个参数,则可能需要查看从OO方法返回的内容,并考虑是否最好返回带有这些参数的对象或结构。因此,您又回到了返回值。


5

您几乎应该始终使用返回值。'out ”参数创建有点摩擦来了很多的API,组合性,等等。

想到的最值得注意的例外是,当您要返回多个值(.Net Framework直到4.0才具有元组)时,例如使用TryParse模式。


不知道这是否是一个好习惯,但是Arraylist也可以用于返回多个值。
BA

@BA不,这不是一个好习惯,其他用户将不知道此Arraylist包含的内容或所处的位置。例如,我想返回读取的字节数和值。或命名的元组会更好。ArrayList或任何其他列表仅在您要返回事物的集合(例如人员列表)时才有用。
sLw

2

在此简单示例中,我希望使用以下内容代替其中的任何一种。

public int Value
{
    get;
    private set;
}

但是,它们几乎都是相同的。通常,只有在他们需要从方法中传回多个值时,才使用“ out”。如果要向方法中传入和传出一个值,则可以选择“ ref”。如果只返回一个值,那么我的方法是最好的,但是如果要传递参数并返回一个值,则可能会选择第一个选择。


2

我认为在少数情况下有用的一种情况是在使用非托管内存时,并且您想清楚地表明“返回的”值应该手动处理,而不是期望自己处理。


2

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

如果函数使用ref或out参数,则不能将其指定为“异步”。

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


关于“异步”名称的要点。以为别人会提到那件事。链接-以及将返回值(实际上是函数本身)用作表达式的方法是另一个关键选择。实际上,仅考虑如何仅从流程中获取内容(“存储桶”-元组/类/结构讨论)与将流程本身视为可以替换单个值的表达式(两者的用处)之间是有区别的。函数本身,因为它仅返回1个值)。
user1172173 17-10-24

1

两者都有不同的用途,编译器对待它们的方式不同。如果您的方法需要返回值,则必须使用return。Out用于需要返回多个值的方法。

如果使用return,则数据将首先写入方法堆栈,然后再写入调用方法的堆栈中。如果发生中断,则将其直接写入调用方法堆栈。不确定是否还有其他差异。


方法堆栈?我不是c#专家,但是x86每个线程仅支持一个堆栈。该方法的“框架”在返回期间被释放,如果发生上下文切换,则可以覆盖释放的堆栈。在c中,所有返回值都放在注册表eax中。如果您想返回对象/结构,则需要将它们分配给堆,并将指针放在eax中。
StefanLundström,2009年

1

正如其他人所说:返回值,而不是参数。

我可以向您推荐《框架设计指南》(第二版)吗?第184-185页介绍了避免出现参数的原因。整本书将指导您正确解决各种.NET编码问题。

与框架设计准则相关的是静态分析工具FxCop的使用。您可以在Microsoft的网站上免费下载该文件。在编译后的代码上运行此代码,然后查看其内容。如果它抱怨成百上千的事情……不要惊慌!冷静仔细地看一下它对每种情况的说法。不要着急尽快解决问题。从告诉您的内容中学习。您将被带入精通的道路。


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;
        }
    }
}

1

没有真正的区别。输出参数在C#中,以允许方法返回多个值,仅此而已。

但是,有一些细微的差异,但其中的任何一个都不重要:

使用out参数将强制您使用以下两行:

int n;
GetValue(n);

而使用返回值将使您可以一行完成:

int n = GetValue();

另一个区别(仅对值类型正确,并且仅当C#不内联函数时才正确)是,当函数返回时,使用返回值必须复制值,而使用OUT参数则不一定这样做。


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;
}

0

返回值是您的方法返回的普通值。

其中,out参数,well out和ref是C#的2个关键字,它们允许传递变量作为reference

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


-2

我怀疑我不会对此问题进行调查,但是我非常经验的程序员,并且我希望一些胸襟开阔的读者能够引起关注。

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

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

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

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

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

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

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

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

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

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

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

因此,很简单,一个好习惯是向正在阅读您的代码的人发出信号,即通过使函数不返回值可能会产生副作用。


>如果有时要调用此函数但不获取计数,则可以始终声明重载。在C#版本7(我认为)和更高版本中,可以使用_丢弃符号忽略out参数,例如:DeleteBasketItems(category,out _);
辩论者
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.