"The operator can’t be unconditionally invoked because the receiver can be null" 迁移到 Dart 空安全后出错

"The operator can’t be unconditionally invoked because the receiver can be null" error after migrating to Dart null-safety

我正在升级一个基于Flutter框架的个人包。我注意到 here 在 Flutter Text 小部件源代码中有一个空检查:

if (textSpan != null) {
  properties.add(textSpan!.toDiagnosticsNode(name: 'textSpan', style: DiagnosticsTreeStyle.transition));
}

但是,textSpan! 仍在使用 ! 运算符。不应该 textSpan 提升为非空类型而不必使用 ! 运算符吗?但是,尝试删除运算符会出现以下错误:

An expression whose value can be 'null' must be null-checked before it can be dereferenced.
Try checking that the value isn't 'null' before dereferencing it.

这是一个独立的例子:

class MyClass {
  String? _myString;
  
  String get myString {
    if (_myString == null) {
      return '';
    }
    
    return _myString; //   <-- error here
  }
}

我得到一个编译时错误:

Error: A value of type 'String?' can't be returned from function 'myString' because it has a return type of 'String'.

或者,如果我尝试获取 _mySting.length,我会收到以下错误:

The property 'length' can't be unconditionally accessed because the receiver can be 'null'.

我认为执行 null 检查会将 _myString 提升为不可为 null 的类型。为什么不呢?

My question 已在 GitHub 上解决,所以我在下面发布了一个答案。

Dart 工程师 Erik Ernst says on GitHub:

Type promotion is only applicable to local variables. ... Promotion of an instance variable is not sound, because it could be overridden by a getter that runs a computation and returns a different object each time it is invoked. Cf. dart-lang/language#1188 for discussions about a mechanism which is similar to type promotion but based on dynamic checks, with some links to related discussions.

所以本地类型推广有效:

  String myMethod(String? myString) {
    if (myString == null) {
      return '';
    }
    
    return myString;
  }

但是实例变量不提升。为此,您需要使用 ! 运算符手动告诉 Dart 您确定实例变量不为空:

class MyClass {
  String? _myString;
  
  String myMethod() {
    if (_myString == null) {
      return '';
    }
    
    return _myString!;
  }
}

错误:

假设,这是您的代码,您正在对实例变量进行空检查,但仍然看到错误:

class Foo {
  int? i = 0;

  double func() {
    if (i != null) return i.toDouble(); // <-- Error
    return -1;
  }
}

The method 'toDouble' can't be unconditionally invoked because the receiver can be 'null'.

您在这样的代码中看到的错误是因为 Getter 未提升为其不可为 null 的对应项。说说原因吧。


错误原因:

比方说,有一个 class Bar 扩展 Foo 并覆盖 i 变量并且不为其分配任何值(保留它 null):

class Bar extends Foo {
  @override
  int? i;
}

所以,如果你能做到

print(Bar().func() * 2);

你会 运行 进入 运行time null 错误,这就是禁止 getters 类型提升的原因。


解决方案:

我们需要摆脱 int? 的可空性。一般有3种方法(更多的方法包括使用asis等)

  • 使用局部变量(推荐)

    double bar() {
      var i = this.i; // <-- Use of local variable.
      if (i != null) return i.toDouble();
      return -1;
    }
    
  • 使用 ?。用 ??

    double bar() {
      return i?.toDouble() ?? -1; // Provide some default value.
    }
    
  • 使用 Bang 运算符 (!)

    只有当您 100% 确定变量 (i) 永远不会 null.

    时,才应使用此解决方案
    double bar() {
      return i!.toDouble(); // <-- Bang operator in play.
    }
    

样式:Theme.of(上下文).textTheme.headline5!.copyWith(

style: Theme.of(context).textTheme.headline5!.copyWith(
                        color: Colors.white

尝试使用 ?或空安全检查器 - !