[在Dart中使用Futures
,我遇到了一个有趣的问题。
import 'dart:async';
class Egg {
String style;
Egg(this.style);
}
Future cookEggs(List<Egg> list) =>
new Future(() =>
['omelette','over easy'].forEach((_) => list.add(new Egg(_)))
);
Future cookOne(Egg egg) => new Future(() => egg = new Egg('scrambled'));
void main() {
List<Egg> eggList = new List();
Egg single;
cookEggs(eggList).whenComplete(() => eggList.forEach((_) => print(_.style));
cookOne(single).whenComplete(() => print(single.style));
}
预期输出是:
omelette
over easy
scrambled
获得cookEggs
的List<Eggs>
函数工作正常,但是访问style
的single
属性失败,并抛出NoSuchMethodError
。
我首先认为这可能与按引用传递有关,但是我不明白为什么Dart会按引用传递List
而不传递Egg
。现在,我认为差异可能与赋值(=
)运算符和List.add()
方法有关。我陷入了那种思路,而且我不知道如何检验我的假设。
有什么想法吗?
程序的输出和堆栈跟踪如下所示:
omelette
over easy
Uncaught Error: The null object does not have a getter 'style'.
NoSuchMethodError: method not found: 'style'
Receiver: null
Arguments: []
Stack Trace:
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:45)
#1 main.<anonymous closure> (file:///path/to/eggs.dart:20:51)
#2 _rootRun (dart:async/zone.dart:719)
#3 _RootZone.run (dart:async/zone.dart:862)
#4 _Future._propagateToListeners.handleWhenCompleteCallback (dart:async/future_impl.dart:540)
#5 _Future._propagateToListeners (dart:async/future_impl.dart:577)
#6 _Future._complete (dart:async/future_impl.dart:317)
#7 Future.Future.<anonymous closure> (dart:async/future.dart:118)
#8 _createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:11)
#9 _handleTimeout (dart:io/timer_impl.dart:292)
#10 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:124)
Unhandled exception:
The null object does not have a getter 'style'.
NoSuchMethodError: method not found: 'style'
Receiver: null
Arguments: []
#0 _rootHandleUncaughtError.<anonymous closure>.<anonymous closure> (dart:async/zone.dart:713)
#1 _asyncRunCallbackLoop (dart:async/schedule_microtask.dart:23)
#2 _asyncRunCallback (dart:async/schedule_microtask.dart:32)
#3 _asyncRunCallback (dart:async/schedule_microtask.dart:36)
#4 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:128)
快速解答:传递给函数cookEggs
和cookOne
的是对objects的引用,而不是对变量的引用(这是真正的按引用传递)。
术语pass-by-reference通常被误认为是pass-references-by-value:许多语言仅具有值传递语义,其中的值是传递的是引用(即没有危险功能的指针)。参见Is Java "pass-by-reference" or "pass-by-value"?
cookEggs(eggList)
……变量eggList
包含对鸡蛋列表的引用。该引用最初是由表达式new List()
提供给您的,同时将其存储在变量cookEggs
中之后,会将其传递给eggList
。在cookEggs
内部,添加到列表是可行的,因为您传递了对实际的现有列表对象的引用。
cookOne(single)
……变量single
仅被声明,因此它在语言运行时被隐式初始化为特殊引用null
。在cookOne
内部,您要替换egg
中包含的引用;由于single
是一个不同的变量,它仍然包含null
,因此当您尝试使用它时代码将失败。
按值传递引用的行为在许多现代语言(Smalltalk,Java,Ruby,Python ...)中很常见。传递对象时,实际上是按值传递(因此要复制)变量的内容,该变量是指向该对象的指针。您永远无法控制对象真正存在的位置。
那些指针被命名为引用,而不是指针,因为它们被限制为抽象出内存布局:您不知道对象的地址,您无法窥视对象周围的字节,甚至不能确保对象存储在内存中的固定位置,或者完全存储在内存中(可以像在Gemstone中那样将对象引用实现为UUID或持久数据库中的键)。
相反,通过引用传递,您将在概念上传递变量本身,而不是其内容。要使用按值传递语言实现按引用传递,您需要将变量归类为ValueHolder对象,这些对象可以传递并可以更改其内容。
这是因为,与Java一样,Dart是always pass-by-value,并且从不传递引用。 Dart中传递和分配的语义与Java中相同。在StackOverflow上有关Java的任何地方查找并按值传递,以了解为什么Java被描述为总是按值传递。诸如pass-by-value和pass-by-reference之类的术语应在所有语言中一致使用。因此Dart应该被描述为始终按值传递。
在实际的“通过引用”中,例如在C ++或PHP中用&
标记参数时,或在C#中用ref
或out
标记参数时,在函数内部对该参数变量的简单赋值(即=
)将具有与简单分配(即=
)给函数外部原始传递变量的效果相同。这在Dart中是不可能的。
[许多Python程序员使用术语pass-by-assignment。这适用于许多其他语言,例如Java或Dart,其中的一切都是对象,没有单独的指针类型。在这样的语言中,按值传递和按引用传递没有特别的意义。这些语言从技术上讲是按值传递的,其中对象的“值”是对其的引用。
传递分配意味着以与普通变量分配等效的方式传递参数。例如,如果我有:
final int x = 42;
final String s = "hello world!";
void foo(int intArgument, String stringArgument) {
...
}
void main() {
foo(x, s);
}
那么行为将等同于:
...
void foo() {
int intArgument = x;
String stringArgument = s;
...
}
void main() {
foo();
}
String stringArgument = s;
时,stringArgument
和s
是引用同一对象的两个独立变量。将stringArgument
重新分配给其他内容不会更改s
所指的内容。但是,如果在适当位置mutate该对象,则stringArgument
和s
都将引用现在修改的对象。