从函数返回多个值

115

除了返回一个对象之外,在函数返回语句中是否有一种方法可以返回多个值,就像我们可以在Go(或其他某些语言)中做的那样?

例如,在Go中,我们可以这样做:

func vals() (int, int) {
    return 3, 7
}

这个能在Dart中实现吗?类似于这样:

int, String foo() {
    return 42, "foobar";
} 

我不知道是否有任何语言真正返回多个值。Python使用与您上面使用的相同的语法,返回一个元组,这只是一个简单的对象。我怀疑Go也是类似的。 - Carcigenicate
请参考以下链接:https://groups.google.com/a/dartlang.org/forum/m/#!topic/misc/j-1I--HjXag - Carcigenicate
1
Lua也可以返回多个值。 - Ridcully
这些现在在Dart 3.0中存在:https://dev59.com/elcO5IYBdhLWcg3wt0D1#76257744 - Kyle Venn
10个回答

159

更新至Dart 3.0

在Dart 3.0中可以使用记录(records)

(int, String) foo() {
    return (42, "foobar");
} 

void main() {
  var (a,b) = foo();
  print("int: ${a}");
  print("String: ${b}");
}

原始答案

Dart不支持多个返回值。

您可以返回一个数组,

List foo() {
  return [42, "foobar"];
}

如果你想要值被键入,可以使用一个类似于https://pub.dev/packages/tuple提供的Tuple类。
另外,也可以参考either来实现返回值或错误的方式。

5
对于tuple的+1赞同,它与Go所做的内置功能最接近。它只是没有内置到Dart中,可能还没有太多需求,他们希望保持语言尽可能轻量级。 - Robert Jack Will
1
有其他的优先事项。像这样的功能已经讨论了几次。目前我们关注于Dart 2.0强类型和泛型函数的设计,以及为2.0之后更快的迭代做大量的准备(统一前端、内核)。接下来可能会加入非空类型和其他内容。我倾向于不添加过多的功能,但我完全信任Dart团队会做出正确的决策。 - Günter Zöchbauer
4
同时,“either”函数也不兼容Dart 2.0。 - zypro
6
将返回类型更改为 List<dynamic>List<Object> - Günter Zöchbauer
@zypro 如果需要一个兼容Dart 2.0的Either实现,请参考dartz - Abion47
1
现在Dart 3已经有记录了,因此这个答案不再正确。 - jamesdlin

25

我想补充一下,Go语言中多返回值的主要用途之一是错误处理,而Dart则使用异常和失败的Promise来处理。当然还有其他一些用例,那么让我们看看在使用显式元组时代码是什么样子的:

import 'package:tuple/tuple.dart';

Tuple2<int, String> demo() {
  return new Tuple2(42, "life is good");
}

void main() {
  final result = demo();
  if (result.item1 > 20) {
    print(result.item2);
  }
}

虽然不是非常简洁,但代码整洁且表现力强。我最喜欢的是,一旦你的快速实验项目真正起飞并开始添加功能并需要增加更多结构以保持控制,它基本上不需要太多改动。

class FormatResult {
  bool changed;
  String result;
  FormatResult(this.changed, this.result);
}

FormatResult powerFormatter(String text) {
  bool changed = false;
  String result = text;
  // secret implementation magic
  // ...
  return new FormatResult(changed, result);
}

void main() {
  String draftCode = "print('Hello World.');";
  final reformatted = powerFormatter(draftCode);
  if (reformatted.changed) {
    // some expensive operation involving servers in the cloud.
  }
}

所以,是的,它并没有比Java有太大的改进,但它能够正常运行、清晰且相当高效地构建用户界面。我真的很喜欢如何能够快速地拼凑东西(有时在工作间歇时间在DartPad上开始),然后在我知道项目将长期存在并增长时再添加结构。


一个常见的函数式编程模式,适用于该用例是 https://pub.dartlang.org/packages/either - Günter Zöchbauer
+1 不要重复造轮子,让我们使用一个包,因为接下来可能会有人需要一个三元组,然后就有了Tuple3,以此类推... - Csaba Toth

17
创建一个类:
import 'dart:core';

class Tuple<T1, T2> {
  final T1 item1;
  final T2 item2;

  Tuple({
    required this.item1,
    required this.item2,
  });

  factory Tuple.fromJson(Map<String, dynamic> json) {
    return Tuple(
      item1: json['item1'],
      item2: json['item2'],
    );
  }
}

随便怎么称呼都可以!

Tuple<double, double>(item1: i1,item2: i2);
or
Tuple<double, double>.fromJson(jsonData);

7

您可以创建一个类来返回多个值。例如:

class NewClass {
  final int number;
  final String text;

  NewClass(this.number, this.text);
}

生成数值的函数:

 NewClass buildValues() {
        return NewClass(42, 'foobar');
      }

打印:

void printValues() {
    print('${this.buildValues().number} ${this.buildValues().text}');
    // 42 foobar
  }

3
Dart正在完成records,本质上是更高级的元组。
应该在 Dart 3 的稳定版本中发布。
它已经可以在 试验标志 中使用了。

一个月,你说呢? - claudekennilol
@claudekennilol 看起来高管们把它推迟到了几天前的 Google IO 上,所以现在已经可以在稳定版上使用了。很快就会发布一个带有示例的软件包。宏也可能会出现这种模式。 - exxjob

2

正如其他答案所指出的那样,Dart 3.0 添加了记录,可以用于以类型安全的方式返回多个值。

对于早期版本的Dart,返回多个值的正确方法是将这些值存储在一个类中,无论是您自己的自定义类还是Tuple。然而,为每个函数定义一个单独的类非常不方便,使用Tuple可能会出现错误,因为成员没有有意义的名称。

另一种(虽然有点丑陋,但不太符合Dart风格)的方法是尝试模仿C和C++通常使用的输出参数方法。例如:

class OutputParameter<T> {
  T value;

  OutputParameter(this.value);
}

void foo(
  OutputParameter<int> intOut,
  OutputParameter<String>? optionalStringOut,
) {
  intOut.value = 42;
  optionalStringOut?.value = 'foobar';
}

void main() {
  var theInt = OutputParameter(0);
  var theString = OutputParameter('');
  foo(theInt, theString);
  print(theInt.value); // Prints: 42
  print(theString.value); // Prints: foobar
}

对于调用者到处使用variable.value可能有点不方便,但在某些情况下这可能是值得的。


0

1
在Stack Overflow上的答案应该提供足够的细节来回答问题。外部链接只应用于提供补充信息或作为您答案的引用。 - yoduh
不必提供视频,你可以提供足够详细的答案。 - undefined

0

现在Dart 3.0(Flutter 3.10.0)已经可以使用了。它们被称为"记录"(完整文档在此处)

以下是我最喜欢的一些例子:

返回类型

(int x, double y) geoLocation(String name) =>
    return (-1, 36.8219);

访问它们:

var record = ('first', a: 2, b: true, 'last');

print(record.$1); // Prints 'first'
print(record.a); // Prints 2
print(record.b); // Prints true
print(record.$2); // Prints 'last'

变量

// Record type annotation in a variable declaration:
(String, int) record;

// Initialize it with a record expression:
record = ('A string', 123);

命名参数!

// Record type annotation in a variable declaration:
({int a, bool b}) record;

// Initialize it with a record expression:
record = (a: 123, b: true);

0
你可以使用Set<Object>来返回多个值,
Set<object> foo() {
     return {'my string',0}
}

print(foo().first) //prints 'my string'

print(foo().last) //prints 0

1
我认为集合并没有顺序,因此使用first/last是不正确的,在我看来。 - Marc Van Daele
集合是有序的,因此您将按照返回的顺序获取项目。 - Saeed Rahmatolahi
显然,默认的Set确实是有序的(请参阅https://api.dart.dev/stable/2.19.6/dart-core/Set-class.html)。但请注意,并非所有的Dart Set都是有序的。(不幸的是,我无法取消我的反对票) - Marc Van Daele

-1
在Dart中的这种情况下,一个简单的解决方案是返回一个列表,然后根据您的要求访问返回的列表。您可以通过索引访问特定的值,或者通过简单的for循环访问整个列表。
List func() {
  return [false, 30, "Ashraful"];
}

void main() {
  final list = func();
  
  // to access specific list item
  var item = list[2];
  
  // to check runtime type
  print(item.runtimeType);
  
  // to access the whole list
  for(int i=0; i<list.length; i++) {
    print(list[i]);
  }
}

https://dart.dev/tools/dart-format - Chris Vilches

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接