Dart null / false / empty checking:如何写得更短?

Dart null / false / empty checking: How to write this shorter?

这是我的代码,除了空字符串、null 和 false 之外的所有内容都为真:

if (routeinfo["no_route"] == "" || routeinfo["no_route"] == null || routeinfo["no_route"] == false) {
    // do sth ...
}

这是我的代码,除了空字符串、null、false 或零之外的所有内容都为真:

if (routeinfo["no_route"] == "" || routeinfo["no_route"] == null || routeinfo["no_route"] == false || routeinfo["no_route"] == 0) {
    // do sth...
}

如何在 Dart 中写得更短?还是不可能?

你可以

if (["", null, false, 0].contains(routeinfo["no_route"])) {
  // do sth
}

我会编写一个辅助函数,而不是内联处理所有内容。

bool isNullEmptyOrFalse(Object o) =>
  o == null || false == o || "" == o;

bool isNullEmptyFalseOrZero(Object o) =>
  o == null || false == o || 0 == o || "" == o;

这避免了重复查找(如 contains 操作),但它的可读性更高。它也不会为每个检查创建一个新的 List 文字(使列表 const 可以解决这个问题)。

if (isNullEmptyOrFalse(routeinfo["no_route"])) { ... }

当努力使一些东西变得简短易读时,制作一个名字恰当的辅助函数通常是最好的解决方案。

(补充:既然Dart有了扩展方法,就可以将功能添加为看似对象上的方法或getter,所以你可以直接写value.isNullOrEmpty)。

如果您的要求只是空的或 null(就像我在搜索结果中看到这个标题时的要求),您可以使用 Dart 的安全导航运算符使其更简洁:

if (routeinfo["no_route"]?.isEmpty ?? true) {
  // 
}

在哪里

  • isEmpty 检查一个空字符串,但是如果 routeinfo 是 null 你不能在 null 上调用 isEmpty,所以我们用
  • 检查 null
  • ?. safe navigation operator 仅当 object 不为 null 时才调用 isEmpty,否则生成 null。所以我们只需要用
  • 检查 null
  • ?? null coalescing operator

如果您的地图是可空类型,那么您必须安全地导航:

if (routeinfo?["no_route"]?.isEmpty ?? true) {
  //
}

来自 Android 和 Kotlin,我更喜欢扩展方法而不是静态辅助方法。在 Dart 2.7 中,您现在也可以使用它:

extension Extension on Object {
  bool isNullOrEmpty() => this == null || this == '';

  bool isNullEmptyOrFalse() => this == null || this == '' || !this;

  bool isNullEmptyZeroOrFalse() =>
      this == null || this == '' || !this || this == 0;
}

现在您可以从代码中的任何位置调用这些方法:

if (anyVariable.isNullOrEmpty()) {
  // do something here
}

您可能需要手动导入 dart class,在其中放置您的扩展方法,例如:

import 'package:sampleproject/utils/extensions.dart';

2020 年末更新

总结

  • isNullisNotNull 外,此答案成立。将来 dart/flutter 中引入 Null Safety 时,它们不再提供类型提升。
  • isNullOrEmpty 这样的其他助手不提供类型提升,因为与调用站点相比,它们处于不同的(子)范围。
  • 我个人的意见是,您可以放弃 isNullisNotNull,但保留其他助手,因为您不应该指望他们为您做类型提升。

说明

示范[​​=77=]

这里演示一下为什么encapsulation/helper-getter of isNull(== null)和isNotNull(!= null)是个很大的问题:

// Promotion works
int definitelyInt(int? aNullableInt) {
  if (aNullableInt == null) { // Promote variable `aNullableInt` of Nullable type `int?` to Non-Nullable type `int`
    return 0;
  }
  return aNullableInt; // Can't be null! This variable is promoted to non-nullable type `int`
}

当您使用的 dart 版本中包含“Null Safety”时,上述代码中的类型提升有效!然而:

// Promotion does NOT work!!!
int definitelyInt(int? aNullableInt) {
  if (aNullableInt.isNull) { // does NOT promote variable `aNullableInt` of Nullable type `int?`
    return 0;
  }
  return aNullableInt; // This variable is still of type `int?`!!!
}

以上不起作用,因为空检查 == null!= null 被封装在一个子范围(不同的堆栈框架)中,并且没有推断出在其中具有这种“提升”效果definitelyInt范围。



2020 年初更新

这是一个修改版本,使用 getters 而不是 instance/class 方法 并覆盖 whitespacesisNullisNotNull.

第二次更新

它现在还可以计算空列表和地图!

第三次更新

为了安全和 modularity/maintainability 添加了私有 getter。

第四次更新

更新了解决 Map 的特定问题的答案,当它为空时无法正确处理!因为 Map 不是 Iterable。通过引入 _isMapObjectEmpty U

解决了这个问题

解决方案

extension TextUtilsStringExtension on String {
  /// Returns true if string is:
  /// - null
  /// - empty
  /// - whitespace string.
  ///
  /// Characters considered "whitespace" are listed [here](
  bool get isNullEmptyOrWhitespace =>
      this == null || this.isEmpty || this.trim().isEmpty;
}

/// - [isNullOrEmpty], [isNullEmptyOrFalse], [isNullEmptyZeroOrFalse] are from [this Whosebug answer](
extension GeneralUtilsObjectExtension on Object {
  /// Returns true if object is:
  /// - null `Object`
  bool get isNull => this == null;

  /// Returns true if object is NOT:
  /// - null `Object`
  bool get isNotNull => this != null;

  /// Returns true if object is:
  /// - null `Object`
  /// - empty `String`s
  /// - empty `Iterable` (list, set, ...)
  /// - empty `Map`
  bool get isNullOrEmpty =>
      isNull ||
      _isStringObjectEmpty ||
      _isIterableObjectEmpty ||
      _isMapObjectEmpty;

  /// Returns true if object is:
  /// - null `Object`
  /// - empty `String`
  /// - empty `Iterable` (list, map, set, ...)
  /// - false `bool`
  bool get isNullEmptyOrFalse =>
      isNull ||
      _isStringObjectEmpty ||
      _isIterableObjectEmpty ||
      _isMapObjectEmpty ||
      _isBoolObjectFalse;

  /// Returns true if object is:
  /// - null `Object`
  /// - empty `String`
  /// - empty `Iterable` (list, map, set, ...)
  /// - false `bool`
  /// - zero `num`
  bool get isNullEmptyFalseOrZero =>
      isNull ||
      _isStringObjectEmpty ||
      _isIterableObjectEmpty ||
      _isMapObjectEmpty ||
      _isBoolObjectFalse ||
      _isNumObjectZero;

  // ------- PRIVATE EXTENSION HELPERS -------
  /// **Private helper**
  ///
  /// If `String` object, return String's method `isEmpty`
  ///
  /// Otherwise return `false` to not affect logical-OR expression. As `false` denotes undefined or N/A since object is not `String`
  bool get _isStringObjectEmpty =>
      (this is String) ? (this as String).isEmpty : false;

  /// **Private helper**
  ///
  /// If `Iterable` object, return Iterable's method `isEmpty`
  ///
  /// Otherwise return `false` to not affect logical-OR expression. As `false` denotes undefined or N/A since object is not `Iterable`
  bool get _isIterableObjectEmpty =>
      (this is Iterable) ? (this as Iterable).isEmpty : false;

  /// **Private helper**
  ///
  /// If `Map` object, return Map's method `isEmpty`
  ///
  /// Otherwise return `false` to not affect logical-OR expression. As `false` denotes undefined or N/A since object is not `Map`
  bool get _isMapObjectEmpty => (this is Map) ? (this as Map).isEmpty : false;

  /// **Private helper**
  ///
  /// If `bool` object, return `isFalse` expression
  ///
  /// Otherwise return `false` to not affect logical-OR expression. As `false` denotes undefined or N/A since object is not `bool`
  bool get _isBoolObjectFalse =>
      (this is bool) ? (this as bool) == false : false;

  /// **Private helper**
  ///
  /// If `num` object, return `isZero` expression
  ///
  /// Otherwise return `false` to not affect logical-OR expression. As `false` denotes undefined or N/A since object is not `num`
  bool get _isNumObjectZero => (this is num) ? (this as num) == 0 : false;
}

假设

这里假设 Dart 2.7 或更高版本支持 Extension Methods

用法

以上是两个分机类。一份用于 Object,一份用于 String。将它们放入文件 separately/together 并在调用站点文件中导入 files/file。

描述

来自 Swift,我倾向于使用像用户 @Benjamin Menrad 的 这样的扩展,但在适用时使用 getter 而不是 method/function —— 反映 Dart 的计算属性像 String.isEmpty.

来自 C#,我喜欢他们的 String 辅助方法 IsNullOrWhiteSpace

我的版本融合了以上两个概念。

命名

重命名扩展 类 任意名称。我倾向于这样命名它们:

XYExtension

其中:

  • X 是 File/Author/App/Unique 名字。
  • Y 是要扩展的类型的名称。

名称示例:

  • MyAppIntExtension
  • DartDoubleExtension
  • TextUtilsStringExtension
  • OHProviderExtension

批评

Why private getters instead of simple this == null || this == '' || this == [] || this == 0 || !this

  • 我认为这更安全,因为它首先将对象转换为我们要将其值与之进行比较的正确类型。
  • 更加模块化,因为更改是私有 getter 的核心,任何修改都会反映到各处。
  • 如果类型检查在私有 getter 中失败,我们 return false 这不会影响根逻辑或表达式的结果。

package:quiver has an isEmpty 函数表示 returns true 如果参数是 null 或空字符串。

自己实现这样的功能也很简单

具有 Null 安全性:

假设,您有一个可为空的 Map 和一个 List,其中包含可为空的值。

Map<String, List?>? map;
List<String?>? list;

要检查 null、空和 false,您可以这样做:

if (map?['key']?.isEmpty ?? false) {...}

if (list?[0]?.isEmpty ?? false) {...}
bool isNullString(String? value) {
      if (value == null || value.isEmpty || value == false) {
        return true;
      } else {
        return false;
      }
    }

并像

一样使用这种方法
isNullString(yourValue)