如何从单个方法返回两个值?

时间:2009-09-23 20:36:49

标签: language-agnostic return-value

如果您需要在一种方法中返回两件事,那么最佳方法是什么?

我理解一个方法应该只做一件事的理念,但是说你有一个运行数据库选择的方法,你需要拉两列。我假设你只想遍历数据库结果集一次,但是你想要返回两列有价值的数据。

我提出的选项:

  1. 使用全局变量来保存退货。 我个人尽量避免使用全局变量。
  2. 将两个空变量作为参数传入,然后在方法内部分配变量,现在这是一个空白。 我不喜欢有副作用的方法。< / em>的
  3. 返回包含两个变量的集合。 这会导致代码混乱。
  4. 构建一个容器类来保存双返回。 这比包含其他集合的集合更自我记录,但似乎只是为了创建一个类而感到困惑返回的目的。

22 个答案:

答案 0 :(得分:15)

这与语言无关:在Lisp中,您实际上可以从函数返回任意数量的值,包括(但不限于)none,one,two,......

(defun returns-two-values ()
  (values 1 2))

对于Scheme和Dylan来说同样如此。在Python中,我实际上会使用包含2个值的元组,如

def returns_two_values():
   return (1, 2)

正如其他人所指出的,您可以使用C#中的out参数返回多个值。在C ++中,您将使用引用。

void 
returns_two_values(int& v1, int& v2)
{
    v1 = 1; v2 = 2;
}

在C中,您的方法将指向位置,您的函数应存储结果值。

void 
returns_two_values(int* v1, int* v2)
{
    *v1 = 1; *v2 = 2;
}

对于Java,我通常使用专用类或非常通用的小助手(目前,我的私有“公共”库中有两个:Pair<F,S>Triple<F,S,T>,两者都不过是2个resp.3值的简单不可变容器)

答案 1 :(得分:14)

我会创建数据传输对象。如果它是一组信息(名字和姓氏),我会创建一个Name类并返回它。 #4是要走的路。它看起来像前面的更多工作(它是),但后来澄清了它。

如果是记录列表(数据库中的行),我会返回某种类型的Collection。

除非应用程序是微不足道的,否则我绝不会使用全局变量。

答案 2 :(得分:12)

不是我自己的想法(鲍勃叔叔):

如果这两个变量之间存在凝聚力 - 我听过他说,你错过了一个课程,那两个是字段。 (他对使用长参数列表的函数说了同样的事情。)

另一方面,如果没有凝聚力,那么该函数不止一件事。

答案 3 :(得分:3)

我认为这完全取决于情景。

从C#心态思考:

1:我会避免使用全局变量来解决这个问题,因为它被认为是不好的做法。

4:如果两个返回值以某种方式或形式唯一地绑定在一起,它可以作为自己的对象存在,那么您可以返回一个包含两个值的对象。如果此对象仅被设计并用于此方法的返回类型,那么它可能不是最佳解决方案。

3:如果返回的值是相同的类型并且可以将其视为集合,则集合是一个很好的选择。但是,如果具体示例需要2个项目,并且每个项目都是“自己的” - >&gt;也许一个代表事物的开始,另一个代表结束,并且返回的项目不是可互换的,那么这可能不是最好的选择。

2:如果4和3对你的场景没有意义,我最喜欢这个选项。如3中所述,如果你想得到两个代表某事物的开始和结束项目的对象。然后我将通过引用使用参数(或者再次使用参数,具体取决于它是如何使用的)。这样您的参数可以明确定义它们的用途:MethodCall(ref对象StartObject,ref对象EndObject)

答案 4 :(得分:3)

我认为最优选的方法是构建一个容器(可能是一个类或一个结构 - 如果你不想为此创建一个单独的类,struct是要走的路),那将容纳所有要返回的参数。

答案 5 :(得分:3)

在C / C ++世界中,通过引用传递两个变量(an example,你的第2号)实际上是很常见的。

答案 6 :(得分:2)

Python(如Lisp)也允许你返回任意数量的 函数的值,包括(但不限于) 没有,一,二

def quadcube (x):
     return x**2, x**3

a, b = quadcube(3)

答案 7 :(得分:2)

我个人尝试使用允许函数返回超过简单整数值的语言。

首先,你应该区分你想要的东西:任意长度的返回或固定长度的返回。

如果你希望你的方法返回任意数量的参数,你应该坚持收集返回。因为这些集合 - 无论你的语言是什么 - 都与完成这样的任务密切相关。

但有时你只需要返回两个值。返回两个值 - 当您确定总是两个值时 - 与返回一个值有什么不同?我说,没办法有所不同!现代语言,包括perl,ruby,C ++,python,ocaml等允许函数返回元组,无论是内置还是第三方语法糖(是的,我在谈论boost::tuple)。它看起来像是:

tuple<int, int, double> add_multiply_divide(int a, int b) {
  return make_tuple(a+b, a*b, double(a)/double(b));
}

在我看来,指定一个“out参数”由于当时学习的旧语言和范例的限制而被过度使用。但是仍然存在很多可用的情况(如果你的方法需要修改作为参数传递的对象,那个对象不是包含方法的类)。

结论是没有通用答案 - 每种情况都有自己的解决方案。但有一个常见的事情是:它不违反任何范例,该函数返回多个项目。这是一种语言限制,后来以某种方式转移到了人的心灵。

答案 8 :(得分:1)

  1. 有些语言使#3原生且容易。示例:Perl。 “返回($ a,$ b);”。 Ditto Lisp。

  2. 除此之外,检查您的语言是否有适合该任务的集合,C ++中的a / pair / tuple

  3. 除此之外,创建一个/ tuple类和/或集合并重新使用它,特别是如果您的语言支持模板化。

答案 9 :(得分:1)

如果我返回两个完全相同的东西,一个集合可能是合适的,但一般来说我通常会建立一个专门的类来完全保留我需要的东西。

如果你今天要从这两列中返回两件事,明天你可能想要第三列。维护自定义对象比任何其他选项都容易得多。

答案 10 :(得分:1)

  

当你处于某种情况时   需要在一个单独的东西中返回两件事   方法,最好的方法是什么?

这取决于你为什么要归还两件事。 基本上,正如这里的每个人似乎都同意的那样,#2和#4是两个最好的答案......

  

我理解a的哲学   方法应该只做一件事,但是   说你有一个运行方法   数据库选择,你需要拉   两列。我只是假设你   想要遍历数据库   结果设置一次,但你想   返回两列有价值的数据。

如果数据库中的两个数据是相关的,例如客户的名字和姓氏,我的确会认为这是“一件事”。 另一方面,假设您提出了一个奇怪的SELECT语句,该语句返回您公司在给定日期的总销售总额,并且还会读取今天首次销售的客户名称。在这里,你做了两件无关的事情! 如果确实这个奇怪的SELECT语句的性能比为两个不同的数据执行两个SELECT语句要好得多,并且经常需要这两个数据(如果你这么整个应用程序会更慢)并没有这样做),然后使用这个奇怪的SELECT可能是一个好主意 - 但你最好准备好证明为什么你的方式真的会对感知的响应时间产生影响。

  

我提出的选项:

     

1使用全局变量来保存回报。我亲自试着避免   全球我可以。

在某些情况下,创建全局是正确的做法。但是“从函数中返回两件事”并不是其中之一。为此目的这样做只是一个坏主意。

  

2将两个空变量作为参数传递,然后分配变量   在方法内部,现在是一个   空隙。

是的,这通常是最好的主意。这正是为什么“通过引用”(或“输出”,取决于您使用的语言)参数的原因。

  

我不喜欢有副作用的方法。

理论很好,但你可以把它放得太远。如果该方法没有保存客户数据的副作用,那么调用SaveCustomer()会有什么意义呢? By Reference参数被理解为包含返回数据的参数。

  

3返回包含两个变量的集合。这可能会导致令人困惑的代码。

真。例如,返回一个数组,其中元素0是第一个名称,元素1是姓氏,这是没有意义的。这将是一个坏主意。

  

4构建一个容器类来保存双返回。这比包含其他集合的集合更自我记录,但似乎只是为了返回而创建一个类可能会令人困惑。

是和否。正如你所说,我不想创建一个名为FirstAndLastNames的对象,只是被一个方法使用。但是如果已经存在一个基本上具有此信息的对象,那么在这里使用它将是完全合理的。

答案 11 :(得分:1)

如果你的函数有返回值,它可能会返回它/它们以分配给变量或隐含变量(例如,执行操作)。任何你可以有用地表达为变量(或者可测试的价值)应该是公平的游戏,并且应该决定你的回归。

您的示例提到了SQL查询中的一行或一组行。然后你应该准备好将它们作为对象或数组来处理,这表明你的问题得到了适当的答案。

答案 12 :(得分:0)

我通常会选择方法#4,因为我更清楚知道函数产生或计算的是它的返回值(而不是byref参数)。此外,它在程序流程中赋予了相当“功能”的风格。

选项#4与通用元组类的缺点是它不比返回集合好(唯一的好处是类型安全)。

public IList CalculateStuffCollection(int arg1, int arg2)
public Tuple<int, int> CalculateStuffType(int arg1, int arg2)

var resultCollection = CalculateStuffCollection(1,2);
var resultTuple = CalculateStuffTuple(1,2);

resultCollection[0]    // Was it index 0 or 1 I wanted? 
resultTuple.A          // Was it A or B I wanted?

我想要一种允许我返回命名变量的不可变元组的语言(类似于字典,但是不可变,类型安全和静态检查)。但遗憾的是,在VB.NET世界中我无法使用这样的选项,它可能在其他地方。

我不喜欢选项#2,因为它打破了“功能”风格并迫使你回到程序世界(当我不想这样做只是为了调用像TryParse这样的简单方法)。

答案 13 :(得分:0)

Haskell还允许使用内置元组的多个返回值:

sumAndDifference        :: Int -> Int -> (Int, Int)
sumAndDifference x y    = (x + y, x - y)

> let (s, d) = sumAndDifference 3 5 in s * d
-16

作为纯语言,不允许选项1和2。

即使使用状态monad,返回值也包含(至少在概念上)所有相关状态的包,包括函数刚刚进行的任何更改。通过一系列操作传递该状态只是一种奇特的惯例。

答案 14 :(得分:0)

使用std :: vector,QList或某个托管库容器来容纳您想要返回的许多X:

QList<X> getMultipleItems()
{
  QList<X> returnValue;
  for (int i = 0; i < countOfItems; ++i)
  {
    returnValue.push_back(<your data here>);
  }
  return returnValue;
}

答案 15 :(得分:0)

您还应该考虑您的方法的设计是主要返回单个值,并且您将获得另一个值以供参考,或者您是否真的有一个可返回的东西,如名字 - 姓氏。

例如,您可能有一个库存模块,用于查询库存中的小部件数量。您要提供的返回值是实际的小部件数。但是,您可能还想记录某人查询库存的频率并返回到目前为止的查询数。在这种情况下,将两个值一起返回可能很诱人。但是,请记住,您可以使用类变量来存储数据,因此您可以存储内部查询计数,而不是每次都返回它,然后使用第二个方法调用来检索相关值。如果它们真正相关,则仅将两个值组合在一起。如果不是,请使用单独的方法单独检索它们。

答案 16 :(得分:0)

我有时使用延续传递样式来解决这个问题,将函数值作为参数传递,并返回传递多个值的函数调用。

用没有第一类函数的语言代替函数值的对象。

答案 17 :(得分:0)

我的选择是#4。在函数中定义参考参数。该指针引用了一个Value Object。

在PHP中:

class TwoValuesVO {
  public $expectedOne;
  public $expectedTwo;
}

/* parameter $_vo references to a TwoValuesVO instance */
function twoValues( & $_vo ) {
  $vo->expectedOne = 1;
  $vo->expectedTwo = 2;
}

在Java中:

class TwoValuesVO {
  public int expectedOne;
  public int expectedTwo;
}

class TwoValuesTest {
  void twoValues( TwoValuesVO vo ) {
    vo.expectedOne = 1;
    vo.expectedTwo = 2;
  }
}

答案 18 :(得分:0)

对于你描述的情况,从单个表中提取两个字段,相应的答案是#4,因为同一实体(表)的两个属性(字段)将表现出强大的内聚力。

你担心“为了回归而创建课程可能会令人困惑”可能不那么现实。如果您的应用程序不重要,您可能需要在其他地方重新使用该类/对象。

答案 19 :(得分:0)

我通常使用元组。我主要使用C#,它很容易设计通用元组结构。我认为对于大多数具有泛型的语言来说它会非常相似。顺便说一句,1是一个可怕的想法,3只有当你得到两个相同类型的返回时才有效,除非你使用的语言中所有东西都来自相同的基本类型(即对象)。 2和4也是不错的选择。 2不会先验地引入任何副作用,它只是笨拙。

答案 20 :(得分:0)

尽管我很难这样做,但我发现在PHP中返回多个值的最可读方式(我使用的是大多数)正在使用(多维)数组,如下所示:

function doStuff($someThing)
{
    // do stuff
    $status  = 1;
    $message = 'it worked, good job';

    return array('status' => $status, 'message' => $message);
}

不漂亮,但它确实有效,并且要弄清楚发生了什么并不是非常困难。

答案 21 :(得分:0)

使用var / out参数或按引用传递变量,而不是按值传递。在德尔斐:

function ReturnTwoValues(out Param1: Integer):Integer;
begin
  Param1 := 10;
  Result := 20;
end;

如果使用var而不是out,则可以预先初始化参数。

对于数据库,每列可以有一个out参数,函数的结果将是一个布尔值,表示是否正确检索了记录。 (虽然我会使用单个记录类来保存列值。)