【问题标题】:How do you return two values from a single method?如何从一个方法返回两个值?
【发布时间】:2010-11-30 21:38:40
【问题描述】:

当您需要在一个方法中返回两个东西时,最好的方法是什么?

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

我想出的选项:

  1. 使用全局变量来保存回报。 我个人尽量避免使用全局变量。
  2. 传入两个空变量作为参数,然后在方法内部分配变量,这现在是一个 void。 我不喜欢有副作用的方法的想法。
  3. 返回一个包含两个变量的集合。 这会导致代码混乱。
  4. 构建一个容器类来保存双重返回。 这比包含其他集合的集合更具自记录性,但似乎仅仅为了以下目的而创建一个类可能会令人困惑一个回报。

【问题讨论】:

  • 哪种哲学说你不应该返回两个变量?职责分离(方法应该只做一件事)并不意味着方法不能返回两个或更多变量。也许一件事是由几个部分组成的。例如状态编号和状态消息。
  • 这与语言无关。答案取决于语言的特性(元组、引用传递、输出参数)。
  • @Dan,看我对stackoverflow.com/questions/1468375/…的评论。
  • @tharkun,这是一个很好的观点。
  • 处理这种情况的正确方法似乎很主观。所以我不会标记答案。

标签: language-agnostic return-value


【解决方案1】:

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

【讨论】:

  • 我认为向量会更好,只是出于缓存原因。
  • @GMan - 我习惯了 QList,它主要是一个隐藏的向量,谢谢。 (尽管如果 OP 真的只返回几个对象,那就没有实际意义了。:-P)
【解决方案2】:

使用 var/out 参数或通过引用而不是值传递变量。在德尔福:

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

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

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

【讨论】:

  • 这只是他列表中#2 的特殊版本(或者#2 是 out params 的特殊版本:)
  • 从可读性的角度来看,这是一种非常令人困惑的方法。大多数人希望参数是函数输入,而不是输出。
  • @Anna,你是对的,但话说回来,大多数人希望函数只返回一个结果,而不是两个。对于少数例外情况,使用 out 参数仍然是最佳解决方案。 (代码完成将通知开发人员该参数是一个输出参数。)
  • @DVK,基本上,它还取决于您使用的语言。在 Delphi(代码示例)中,解决方案 #2 恰好是最常见的解决方案。 Pascal 最初无法将结构作为函数结果返回,因此结构通常作为参数返回,并带有一些指示成功或错误的附加结果值。
  • @Workshop Alex:确实如此,但重构方法或使用 DTO/集合对我来说似乎更直接。
【解决方案3】:

我会创建数据传输对象。如果它是一组信息(名字和姓氏),我会创建一个 Name 类并返回它。 #4是要走的路。前面的工作似乎更多(确实如此),但稍后会更清楚地说明。

如果它是记录列表(数据库中的行),我会返回某种集合。

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

【讨论】:

  • (+1) 执行此操作时,您实际上将方法更改为再次返回单个值。 :-)
【解决方案4】:

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

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

【讨论】:

  • 但是再增加一个类确实会增加更多代码、更多源文件,从而增加复杂性。这是另一个需要创建和释放的对象。
  • 为什么投反对票?这与投票最多的答案相同,但在这种情况下,它被赋予了更好的名称 (DTO)。在需要时添加类不会增加复杂性,这就是它们的目的!在这种情况下,一个专门的类提供的清晰度远远超过了添加一个额外类的复杂性。
【解决方案5】:
  1. 某些语言使#3 变得本地化且容易。示例:Perl。 “返回($a,$b);”。 Lisp 同上。

  2. 除此之外,请检查您的语言是否有适合该任务的集合,例如 C++ 中的对/元组

  3. 除此之外,创建一个对/元组类和/或集合并重新使用它,尤其是在您的语言支持模板的情况下。

【讨论】:

    【解决方案6】:

    尽管这样做让我很痛苦,但我发现在 PHP 中返回多个值的最易读的方法(这是我主要使用的方法)是使用(多维)数组,如下所示:

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

    不漂亮,但它很有效,而且弄清楚发生了什么并不难。

    【讨论】:

    • 您应该编辑您的帖子以展示如何使用 list($var1, $var2) 来解压数组。
    • 这个解决方案有什么不好的地方?我认为这不会使代码不可读。但它不是多维数组,它只是一个包含两个键 => 值对的简单数组。
    • 让这个非常不好看的是它强制调用者和 doStuff 之间的紧密耦合。如果您不相信我,请尝试编写使用返回值的调用程序而不查看 DoStuff 的实现。
    • @inkedmn:您将多维与联想混合在一起。您的示例显示了 PHP 中所谓的关联数组。 @JohnFx:除非调用者只是传递对象,否则我看不出任何其他实现不需要您查看它的实现(或至少在文档中)。返回任何其他类型会如何丢失耦合?
    • 我在 JavaScript 中使用对象执行此操作。如果你小心,它可以使代码简洁明了。
    【解决方案7】:

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

    【讨论】:

      【解决方案8】:

      这并不完全与语言无关:在 Lisp 中,您实际上可以从函数返回任意数量的值,包括(但不限于)无、一、二……

      (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&lt;F,S&gt; 和 Triple&lt;F,S,T&gt;,它们都只是简单的不可变容器2 和 3 值)

      【讨论】:

      • 是的,你绝对正确,这并不完全不可知论。仍然有针对某些语言组的解决方案。例如,您提供的 C++ 解决方案可以用 Java 实现,这是我实际使用的语言。
      • 不。在 C++ 中,您可以使用类似于 void method(int&amp; out_arg_1, int&amp; out_arg_2) { ... } 的东西,这在 Java 中是不可能的。 Java 中没有任何类似于引用(在 C++ 意义上)。
      • Java 标准库中的 org.omg.CORBA.IntHolder 有点类似于 C++ 中的引用,旨在用作输出参数。但我从未见过它在惯用代码中使用过。
      • @Dirk,对于原语来说,不可能通过引用传递,但是对象的访问器使用对原始内存空间的引用,相当于 C++ 中的 ->。
      • @nemo:是的。但是,如果使用预构建的容器对象来传递返回值将是您的 #2 或 Pete Kirkham 提到的 IntHolder 的特例。
      【解决方案9】:

      我认为最首选的方法是构建一个容器(可以是一个类或结构 - 如果您不想为此创建一个单独的类,结构是可行的方法),它将容纳所有要返回的参数。

      【讨论】:

        【解决方案10】:

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

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

        如果您希望您的方法返回任意数量的参数,您应该坚持使用集合返回。因为这些集合——无论你的语言是什么——都是为了完成这样的任务而专门绑定的。

        但有时您只需要返回两个值。返回两个值(当您确定它总是两个值时)与返回一个值有何不同?没办法,我说!现代语言,包括 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 参数”被过度使用,因为当时学习的旧语言和范式的局限性。但是仍然有很多情况下它是可用的(如果您的方法需要修改作为参数传递的对象,该对象不是包含方法的类)。

        结论是没有通用的答案——每种情况都有自己的解决方案。但是有一个共同点:函数返回多个项目并不违反 any 范式。这是一种语言限制,后来不知何故转移到了人类的脑海中。

        【讨论】:

        • 哦,我没有提到全局变量。因为他们很烂。期间。
        【解决方案11】:

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

        【讨论】:

          【解决方案12】:

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

          如果这两个变量之间存在凝聚力 - 我听他说过,你错过了一个将这两个变量作为字段的类。 (他对具有长参数列表的函数也说过同样的话。)

          另一方面,如果没有内聚,那么函数会做不止一件事。

          【讨论】:

            【解决方案13】:

            对于您描述的情况,从单个表中提取两个字段,鉴于同一实体(表)的两个属性(字段)将表现出很强的内聚力,正确的答案是 #4。

            您担心“仅仅为了返回而创建一个类可能会令人困惑”可能并不现实。如果您的应用程序不平凡,您可能需要在其他地方重用该类/对象。

            【讨论】:

              【解决方案14】:

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

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

              【讨论】:

                【解决方案15】:

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

                从 C# 的心态思考:

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

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

                3:如果返回的值是相同的类型并且可以被认为是一个集合,那么集合是一个很好的选择。但是,如果具体示例需要 2 个项目,并且每个项目都是“自己”的东西 -> 可能一个代表某事的开始,另一个代表结束,并且返回的项目不能互换使用,那么这可能不是是最好的选择。

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

                【讨论】:

                  【解决方案16】:

                  我的选择是#4。在函数中定义引用参数。该指针引用了一个值对象。

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

                  【讨论】:

                    【解决方案17】:

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

                    定义四立方(x): 返回 x**2, x**3 a, b = 四立方(3)

                    【讨论】:

                      【解决方案18】:

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

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

                      【讨论】:

                        【解决方案19】:

                        当你处于你 需要一次返回两个东西 方法,最好的方法是什么?

                        这取决于您返回两件东西的原因。 基本上,这里的每个人似乎都同意,#2 和 #4 是两个最佳答案...

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

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

                        我想出的选项:

                        1 使用全局变量来保存收益。我个人尽量避免 全局变量,我可以。

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

                        2 传入两个空变量作为参数,然后分配变量 在方法内部,现在是 无效。

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

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

                        很好的理论,但你可能太过分了。如果该方法没有保存客户数据的副作用,那么调用 SaveCustomer() 有什么意义? By Reference 参数被理解为包含返回数据的参数。

                        3 返回一个包含两个变量的集合。这可能会导致代码混乱。

                        没错。例如,返回一个元素 0 是名字而元素 1 是姓氏的数组是没有意义的。这是个坏主意。

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

                        是和不是。正如您所说,我不想创建一个名为 FirstAndLastNames 的对象,只是供一种方法使用。但是如果已经有一个对象基本上具有这些信息,那么在这里使用它就很有意义了。

                        【讨论】:

                          【解决方案20】:

                          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 是不允许的。

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

                          【讨论】:

                            【解决方案21】:

                            我通常会选择方法#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 这样的简单方法)。

                            【讨论】:

                              【解决方案22】:

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

                              在没有一等函数的语言中用对象代替函数值。

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 2014-12-22
                                • 2019-09-28
                                • 1970-01-01
                                • 2018-07-17
                                • 2012-01-17
                                • 2014-05-27
                                相关资源
                                最近更新 更多