在Dart中是否有一种方法可以通过引用传递原始参数?

84

我想通过引用方式传递基本类型(int,bool等)。 我在这里找到了一篇相关讨论文章(段落“按引用传递值类型”):Dart中的值类型,但我仍然想知道是否有一种方法可以在Dart中实现它(除了使用对象包装器)? 有任何开发进展吗?


我很好奇,你的使用场景是什么?为什么你想要这样做? - Shailen Tuli
对不起,我不理解这个注释。我只是试图理解提问者的意图。 - Shailen Tuli
我并不感到惊讶(好奇)有人需要它。同时,我也不感到惊讶在Dart语言中它是缺失的。它很有用,但并非关键。 - mezoni
了解为什么有人想做某事,可以提出实现相同效果的替代方法。 - Pixel Elephant
1
@ShailenTuli 我也很好奇。我使用了一个包装器,问题得到了解决,但我想知道Dart团队是否有计划让我们决定何时需要复制或引用,无论我们使用对象还是基本类型。 - Eric Lavoie
显示剩余5条评论
8个回答

76

Dart语言目前不支持此功能,未来也许会有,但只能等待时间证明。

原始类型将按值传递,正如在此已经提到的那样,“通过引用传递原始类型”的唯一方法是将其封装起来:

class PrimitiveWrapper {
  var value;
  PrimitiveWrapper(this.value);
}

void alter(PrimitiveWrapper data) {
  data.value++;
}

main() {
  var data = new PrimitiveWrapper(5);
  print(data.value); // 5
  alter(data);
  print(data.value); // 6
}

如果您不想那样做,那么您需要找到解决问题的另一种方法。

有些情况下,我看到人们需要通过引用传递变量是因为他们想要将某个值传递给类中的函数:

class Foo {
  void doFoo() {
    var i = 0;
    ...
    doBar(i); // We want to alter i in doBar().
    ...
    i++;
  }

  void doBar(i) {
    i++;
  }
}
在这种情况下,你可以将 i 改为一个类成员。

你的包装器不是通用的,但语言允许这种改进。 - mezoni
5
如果你喜欢通用品,那当然可以去尝试。我相信这是解释这一点最简单的方法。 :) - Kai Sellgren
达特语言不支持这个功能,我怀疑它永远不会支持。问题在哪里?在Dart和Dart2JS中实现此功能是可能的。许多提议的功能都可以实现并结构化为有效地编译成Javascript。问题在于Dart团队专注于修复错误和Web客户端改进。我认为这将在未来实现。我没有发现阻止这个功能实现的问题。我认为你的工作与编译器设计相距甚远?我是对的吗? - mezoni
2
我从未说过有问题。"我怀疑"的意思是我怀疑该功能是否会到来。这仅仅是一个观点,我基于目前在Dart世界中听到和看到的东西得出的。Dart有一定的目标群体和特定的用例。该功能可能会在未来到来,也可能不会,猜测毫无意义。这就像猜测是否可能为类型定义方差。我怀疑这永远不可能实现,但我无法确定。 - Kai Sellgren
从@Eric的问题链接中可以看到:“不支持按值传递并不是由于与Javascript的交互。这是对象模型的核心部分。在Smalltalk、Ruby、Java等语言中都是如此。对象是通过引用传递的。” - Benji XVI

7
不行,包装器是唯一的方法。

6
它们是按引用传递的。只是因为“原始”类型没有更改其内部值的方法,所以这并不重要。
如果我理解有误,请纠正我,也许您误解了“按引用传递”的含义?我假设您想要做的事情类似于param1 = 10,并且希望在从方法返回时仍保持此值为10。但是引用不是指针。当您使用=运算符为参数分配新值时,这种更改不会反映在调用方法中。这对非原始类型(类)仍然成立。
例如:
class Test {
  int val;
  Test(this.val);
}

void main() {
  Test t = new Test(1);
  fn1(t);
  print(t.val); // 2
  fn2(t);
  print(t.val); // still 2, because "t" has been assigned a new instance in fn2()
}

void fn1(Test t) {
  print(t.val); // 1
  t.val = 2;
}

void fn2(Test t) {
  t = new Test(10);
  print(t.val); // 10
}

编辑 根据评论,我尝试使我的回答更加清晰,但不知何故,我似乎无法在不引起更多混乱的情况下表达得恰当。基本上,当一个从Java转来的人说“参数按引用传递”时,他们的意思就是C/C++开发者所说的“参数作为指针传递”。


@mezoni:你的定义难道不正是“按引用传递”的意思吗?如果参数是按值传递的,那么我的例子中的fn1()函数不会像它现在这样工作,对吧? - MarioP
我的意思是在Dart中没有真正的原始类型。num、int、double和bool都是对象,当作参数传递时,与“复杂”类别的行为相同。此外,我认为我们的意思是相同的,只是称呼不同。“按引用传递”指传递对象的引用,并且在函数返回后仍然会保留对象成员的更改。现在我们是否心意相通了呢? :) - MarioP
如果有人有任何想法可以让我在我的回答中更清楚,请随意编辑它,因为老实说我不知道该怎么表达。 :-/ - MarioP
@mezoni 啊,现在我明白了。我使用的是Java术语,其中“按引用传递”与C/C++中的含义不同。如果可能的话,我会编辑我的答案以反映这种差异,但不会让它变得更加混乱。:-S - MarioP
1
我认为“按复制/值传递”意味着“发送一个副本,原始对象无法被修改”,而“按引用传递”意味着“发送对象/地址本身,它可以被修改”。 - Eric Lavoie
@EricLavoie 是的。当你按值传递参数的时候,这个值是对象的引用,你传递的是引用的副本而不是对象的副本。但这并不意味着你是按引用传递参数。在这种情况下,对象的引用是参数(按值), 你直接传递它(按值)。传递对象的引用或原始值的引用的引用意味着通过引用传递参数。 - mezoni

3

使用 List 中的值来通过引用传递变量是一种方式。因为数组或列表默认情况下是按引用传递的。

void main() {
List<String> name=['ali' ,'fana'];
updatename(name);
print(name);
}
updatename(List<String> name){
name[0]='gufran';
}

试试这个,这是传递引用最简单的方式之一。


3
作为Dart编译成JavaScript的语言,我尝试了一些适用于JS的东西,猜猜怎么着?!它也适用于Dart!基本上,您可以将值放入对象中,然后在该函数内对该字段值进行的任何更改也会影响该函数外的值。 代码(您可以在dartpad.dev上运行此代码)
main() {
  var a = {"b": false};
  
  print("Before passing: " + a["b"].toString());
  trial(a);
  print("After passing: " + a["b"].toString());
}

trial(param) {
  param["b"] = true;
}

输出

Before passing: false
After passing: true

6
关于原始类型(如int,double,String等),你正在使用哈希映射,即包装器。你的示例失败了。 - Michael Kanzieper

1

只是为了明确:

void main() {
  var list1 = [0,1,2];
  var modifiedList1 = addMutable(list1, 3);
  
  var list2 = [0,1,2];
  var modifiedList2 = addImmutable(list2, 3);

  print(list1);
  print(modifiedList1);
  
  print(list2);
  print(modifiedList2);
}

List<int> addMutable(List<int> list, int element){
  return list..add(element);
}

List<int> addImmutable(List<int> list, int element){
  return [...list, element];
}

输出:

[0, 1, 2, 3]
[0, 1, 2, 3]
[0, 1, 2]
[0, 1, 2, 3]

所有变量都是按值传递的。如果一个变量包含一个原始类型(int、bool等),那就是它的值。你可以随意使用它,不会影响源值。如果一个变量包含一个对象,它实际上包含的是对该对象的引用。

引用本身也是按值传递的,但它所引用的对象根本没有被传递。它只停留在原地。这意味着你实际上可以更改这个对象。

因此,如果你传递一个List并且使用.add()添加了一些内容,你已经在内部更改了它,就像它是按引用传递的一样。但是,如果你使用展开运算符[...list],你将创建一个全新的副本。在大多数情况下,这才是你真正想要做的。

听起来很复杂。其实不是。Dart很酷。


0
如果 ValueNotifier + ValueListenable 对您不起作用(您想确保客户端不会监听值的每个更改,或者您的包是纯 Dart 包,因此无法引用 Flutter 库),请使用一个函数:
class Owner {
  int _value = 0;

  int getValue() => _value;

  void increase() => _value++;
}

void main() {
  final owner = Owner();
  int Function() obtainer = owner.getValue;

  print(obtainer());
  owner.increase();
  print(obtainer());
}

输出将会是:

0
1

这种方法有与内存使用相关的缺点:获取器将保留对所有者的引用,即使所有者已经没有被引用,但获取器仍然可访问, 所有者也将是可访问的,因此不会被垃圾回收。

如果您不想要这个缺点,请传递比整个所有者更小的容器:

import 'package:flutter/foundation.dart';

class ListenableAsObtainer<T> implements ValueObtainer<T> {
  ListenableAsObtainer(this._listenable);

  final ValueListenable<T> _listenable;

  @override
  T get value => _listenable.value;
}

class FunctionAsObtainer<T> implements ValueObtainer<T> {
  FunctionAsObtainer(this._function);

  final T Function() _function;

  @override
  T get value => _function();
}

class ValueAsObtainer<T> implements ValueObtainer<T> {
  ValueAsObtainer(this.value);

  @override
  T value;
}

/// Use this interface when the client needs
/// access to the current value, but does not need the value to be listenable,
/// i.e. [ValueListenable] would be too strong requirement.
abstract class ValueObtainer<T> {
  T get value;
}

FunctionAsObtainer 的使用仍会导致持有者无法进行垃圾回收,但另外两个选项则不会。


0

你可以使用ValueNotifier

并且,你可以将它作为ValueListenable传递给需要了解最新值但不应编辑它的类或方法:

class Owner {
  final theValue = ValueNotifier(true);
  final user = User(theValue);
  ...
}

class User {
  final ValueListeneble<bool> theValue;
  
  User(this.theValue);

  ...
}


它提供了比实际需要更多的功能,但解决了问题。


这种用法可以吗?如果不使用Builder,使用ValueNotifier是否有意义? - bounxye

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