如何在Dart中为枚举类型添加方法或值?

216
在Java中,当你定义一个枚举时,可以像下面这样做,即向enum添加成员。在Dart中是否也可以这样做?
enum Foo {
  one(1), two(2);
  final num value;
  Foo(this.value);
}

我真的不知道为什么Java / Dart领域如此抵制“枚举”实现。没有“枚举”就不是类/常数,如果需要将“枚举”实现为类,则取决于情况,我们仍然可以这样做。在“枚举”中的一行代码需要在所有其他替代方案中编写> 10行代码。因此,它不是一种语法糖。 - s k
11个回答

396

从 Dart 2.6 开始,您可以在类上定义扩展(包括枚举)。

enum Cat {
  black,
  white
}

extension CatExtension on Cat {

  String get name {
    switch (this) {
      case Cat.black:
        return 'Mr Black Cat';
      case Cat.white:
        return 'Ms White Cat';
      default:
        return null;
    }
  }

  void talk() {
    print('meow');
  }
}

例子:

Cat cat = Cat.black;
String catName = cat.name;
cat.talk();

这里是另一个实时示例(使用常量映射而不是 switch 语句):https://dartpad.dartlang.org/c4001d907d6a420cafb2bc2c2507f72c


能否扩展枚举值?比如添加一个新值或删除一个值? - Phani Rithvij
@PhaniRithvij 是的,就像我在上面的例子中展示的那样,这是可能的。你也有不同的选项来实现这个。同时请查看dartpad链接。 - vovahost
不,我的意思是更改原始枚举中的条目。例如 enum X{a, b}; 在定义扩展后将具有 3 个条目。即 enum X{a, b, c} 类似于 extension MyEnum on X{ /* 在此处修改值 */ } - Phani Rithvij
@PhaniRithvij 你不能这样做。枚举的整个目的就是拥有那些静态常量。请使用普通类代替。 - vovahost
@AdamStyrc 目前不可能。请参见 https://github.com/dart-lang/language/issues/41 - vovahost
显示剩余6条评论

215

Dart增强枚举类

从Dart 2.17开始,引入了增强枚举类功能。因此,问题中的示例将如下所示:

enum Foo {
  one(1),
  two(2);

  const Foo(this.value);
  final num value;
}

现在,您可以像这样使用枚举类:
void main() {
  const foo = Foo.one;
  print(foo.value); // 1
}

请注意,由于此功能需要Dart 2.17,因此您需要更新SDK约束:
environment:
  sdk: '>=2.17.0-0 <3.0.0'

添加成员

使用增强型枚举,只要构造函数为const,您就可以向枚举中添加任何成员。

这也意味着您可以向现有的枚举中添加getter或方法,例如:

enum Cake {
  cherry,
  apple,
  strawberry;

  String get description => '$name cake';
}

泛型

增强型枚举类还允许您在枚举中使用泛型。如果将其与成员结合使用,您可以执行以下操作:

enum Bar<T extends Object> {
  number<int>(42),
  name<String>('creativecreatorormaybenot'),
  baz(true); // Note that type inference also works.

  const Bar(this.value);
  final T value;
}

混入和接口

除了声明成员外,你还可以使用增强的枚举来混入 mixins 和实现接口,并覆盖任何缺失的实现。

mixin Foo {
  int get n;
}

abstract class Bar {
  void printNumber();
}

enum Baz with Foo implements Bar {
  one(1),
  two(2);
  
  const Baz(this.n);

  @override
  final int n;

  @override
  void printNumber() => print(n);
}

多个参数

最后请注意,即使我在上面的任何示例中都没有使用它,也可以拥有任意数量的参数(和初始化列表):

enum Foo {
  bar(42, description: 'The answer to life, the universe, and everything.'),
  baz(0, enabled: false, description: 'noop');

  const Foo(
    int number, {
    this.enabled = true,
    required this.description,
  }) : n = number;
  final int n;
  final bool enabled;
  final String description;
}

泛型有什么用途?混合和接口呢?我想我的思维还停留在老式的枚举上。 - Suragch
好的,我可以想到使用 mixins 和 interfaces 的用例,但我真的想不出泛型的严肃用例。 - Suragch
2
@Suragch 我会认为所有这些功能都是为了完整性而添加的,没有更深层次的动机。虽然泛型总是有用例的。比如说,你想向类中添加一个方法,根据泛型类型的不同而表现出不同的行为。 - creativecreatorormaybenot
泛型可能是实现类似于 Kotlin 密封类的一种方式。 - Robert Estivill
当你想要在枚举中存储数据并且希望限制数据的类型时,你可以使用枚举泛型,与泛型类的使用方式相同。例如,假设你想要一种详尽的方式来指定子页面的静态配置数据:https://dartpad.dev/?id=200aded3bd14a91a93ec0a5e9c92ae52(我不确定我会在实践中使用这个具体的例子,但我肯定可以看到这个概念在组织页面/路由声明方面非常有用)。 - Abion47

39

Dart 枚举仅适用于最简单的情况。如果您需要更强大或更灵活的枚举,请使用类并添加静态常量字段,如https://dev59.com/XGUo5IYBdhLWcg3wrhBD#15854550 中所示。

这样您就可以添加任何您需要的内容。


4
如果将枚举与switch结合使用,如果您没有涵盖所有枚举值,则会发出警告。您是否知道是否有一种方法可以通过自定义类实现此行为?该方法是为了维护性目的。 - Rémi Rousselet
1
无法使用自定义类来获取此内容。请参阅https://github.com/dart-lang/sdk/issues/34847。 - Günter Zöchbauer

34

不可以。在Dart中,枚举只能包含枚举的项:

enum Color {
  red,
  green,
  blue
}

然而,枚举类型中的每个项都自动与一个索引号相关联:

print(Color.red.index);    // 0
print(Color.green.index);  // 1

您可以通过它们的索引号获取值:
print(Color.values[0] == Color.red);  // True

参见:https://www.dartlang.org/guides/language/language-tour#enums


MyEnum.values 就是我一直想要的,再加上扩展方法就足够了。 - om-ha

29

可能不符合"Effective Dart"标准,我在Helper类中添加了一个静态方法(Dart中没有伴生对象)。

在你的color.dart文件中。

enum Color {
  red,
  green,
  blue
}

class ColorHelper{

  static String getValue(Color color){
    switch(color){
      case Color.red: 
        return "Red";
      case Color.green: 
        return "Green";
      case Color.blue: 
        return "Blue";  
      default:
        return "";
    }
  }

}

由于该方法与枚举在同一文件中,所以一个导入就足够了

import 'package:.../color.dart';

...
String colorValue = ColorHelper.getValue(Color.red);

11

我做了这个(受到@vovahost的接受答案的启发)

enum CodeVerifyFlow {
  SignUp, Recovery, Settings
}

extension CatExtension on CodeVerifyFlow {
  String get name {
    return ["sign_up", "recovery", "settings"][index];
  }
}

// use it like
CodeVerifyFlow.SignUp.name

感谢我以后再说吧!

9

扩展是有用的,但它不能添加静态方法。如果您想做像 MyType.parse(string) 这样的操作,请考虑使用带有静态const字段的类,而不是扩展(如Günter Zöchbauer之前建议的那样)。

以下是一个例子

class PaymentMethod {
  final String string;
  const PaymentMethod._(this.string);

  static const online = PaymentMethod._('online');
  static const transfer = PaymentMethod._('transfer');
  static const cash = PaymentMethod._('cash');

  static const values = [online, transfer, cash];

  static PaymentMethod parse(String value) {
    switch (value) {
      case 'online':
        return PaymentMethod.online;
        break;
      case 'transfer':
        return PaymentMethod.transfer;
        break;
      case 'cash':
        return PaymentMethod.cash;
      default:
        print('got error, invalid payment type $value');
        return null;
    }
  }

  @override
  String toString() {
    return 'PaymentMethod.$string';
  }
}

我发现这比使用辅助函数更方便。
final method = PaymentMethod.parse('online');
assert(method == PaymentMethod.online);

事实上,无法基于值创建某种工厂使得扩展有些棘手。您可以在扩展上创建一个静态的 parse 方法,但是它只能通过 ExtensionName.parse() 调用,这对最终用户来说不太可发现。我会更新我的答案以指出这一点。 - Bouke Versteegh
为了进一步改进这个解决方案,您可以将字符串存储在const Map<String, PaymentMethod>中,在解析时只返回该键的值,并返回null或抛出错误。请参阅此处:https://gist.github.com/boukeversteegh/f67e58f500f3c0c7126640bb43e11197 - Bouke Versteegh

8
在Dart中即将推出一项名为“增强型枚举”的功能,它允许使用类似于类的特性声明枚举。例如:
enum Blah {
  one(1), two(2);
  final num value;
  const Blah(this.value);
}

该功能尚未发布(请注意,有几个问题尚未解决),但是可以通过使用新版本的工具并传递--enable-experiment=enhanced-enums来进行实验。

结果是Blah是一个枚举声明,其包含两个值Blah.oneBlah.two,且Blah.one.value == 1Blah.two.value == 2。当前的“开发版本”在常见的前端处理此示例(因此dartdart2js将处理它),但分析器尚未处理。


一旦这个功能进入主流的Dart,我会将这个答案标记为被接受的。然而,我并没有进行Dart开发或者关注Dart新闻,所以除非有人在这里通知我,否则两者之间可能会有相当长的时间差。 - Digital Deception
当前状态:增强的枚举特性在版本2.17.0-266.5.beta中默认启用,详见https://dart.dev/get-dart/archive。 - Erik Ernst
谢谢您的更新,Erik。不幸的是,我已经接受了https://dev59.com/JVkT5IYBdhLWcg3wN8-W#71412047作为它指出了何时会成为主流,并提供了更多关于如何使用这些内容的细节。 - Digital Deception

5
作为对使用扩展的其他建议的改进,您可以将分配的值定义为列表或映射,并且扩展将更加简洁。
enum Numbers {
  one,
  two,
  three,
}

// Numbers.one.value == 1
// Numbers.two.value == 2
// Numbers.three.value == 3

带有列表的示例

extension NumbersExtensionList on Numbers {
  static const values = [1, 2, 3];
  int get value => values[this.index];
}

使用地图的示例

extension NumbersExtensionMap on Numbers {
  static const valueMap = const {
    Numbers.one: 1,
    Numbers.two: 2,
    Numbers.three: 3,
  };
  int get value => valueMap[this];
}

注意:这种方法的限制是您无法在枚举上定义一个静态工厂方法,例如Numbers.create(1) (截至Dart 2.9)。您可以在NumbersExtension上定义此方法,但需要像NumbersExtension.create(1)那样调用。


2
我认为这是最好的选择,因为 Map 可以确保添加额外值的安全性。使用列表的解决方案中,更改/删除/添加枚举类型和相应值总是很危险的。 - Heikkisorsa

0

对于字符串的返回:

enum Routes{
  SPLASH_SCREEN,
  HOME,
  // TODO Add according to your context
}

String namedRoute(Routes route){
  final runtimeType = '${route.runtimeTypes.toString()}.';
  final output = route.toString();
  return output.replaceAll(runtimeType, "");
}

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