Dart 空安全不适用于 class 字段
Dart null safety doesn't work with class fields
我已将我的 Dart 代码迁移到 NNBD / Null Safety。其中一些看起来像这样:
class Foo {
String? _a;
void foo() {
if (_a != null) {
_a += 'a';
}
}
}
class Bar {
Bar() {
_a = 'a';
}
String _a;
}
这会导致两个分析错误。对于 _a += 'a';
:
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.
对于Bar() {
:
Non-nullable instance field '_a' must be initialized.
Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
在这两种情况下,我都完全按照错误提示做了!这是怎么回事?
我正在使用 Dart 2.12.0-133。2.beta(12 月 15 日星期二)。
编辑:我发现 this page 上面写着:
The analyzer can’t model the flow of your whole application, so it can’t predict the values of global variables or class fields.
但这对我来说没有意义 - 在这种情况下,从 if (_a != null)
到 _a += 'a';
只有一种可能的流量控制路径 - 没有异步代码并且 Dart 是单线程的 - 所以_a
不是本地的并不重要。
Bar()
的错误消息明确说明了在构造函数中初始化字段的可能性。
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 Foo {
String? _a;
void foo() {
final a = _a;
if (a != null) {
a += 'a';
_a = a;
}
}
}
虽然有点糟糕。我的代码现在充满了将 class 成员复制到局部变量并再次复制回来的代码。 :-/
Non-nullable instance field '_a' must be initialized. Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
啊原来“字段初始化器”实际上是这样的:
class Bar {
Bar() : _a = 'a';
String _a;
}
问题是 class 字段可以被覆盖,即使它被标记为 final
。下面的例子说明了这个问题:
class A {
final String? text = 'hello';
String? getText() {
if (text != null) {
return text;
} else {
return 'WAS NULL!';
}
}
}
class B extends A {
bool first = true;
@override
String? get text {
if (first) {
first = false;
return 'world';
} else {
return null;
}
}
}
void main() {
print(A().getText()); // hello
print(B().getText()); // null
}
B
class 覆盖 text
final 字段,因此它 return 是第一次被询问时的值,但 return 是 null
在此之后。您不能以可以阻止允许这种形式的覆盖的方式编写 A
class。
所以我们无法将 getText
的 return 值从 String?
更改为 String
,即使看起来我们检查了 text
字段中的 null
在 return 之前。
处理这种情况的方法很少。我给了一个 所以我只写它的解决方案:
使用局部变量(推荐)
void foo() {
var a = this.a; // <-- Local variable
if (a != null) {
a += 'a';
this.a = a;
}
}
使用??
void foo() {
var a = (this.a ?? '') + 'a';
this.a = a;
}
使用 Bang 运算符 (!)
只有当您 100% 确定变量 (a
) 在您使用时不是 null
时,才应使用此解决方案。
void foo() {
a = a! + 'a'; // <-- Bang operator
}
回答你的第二个问题:
不可为 null 的字段应始终进行初始化。一般有3种初始化方式:
声明中:
class Bar {
String a = 'a';
}
初始化中
class Bar {
String a;
Bar({required this.a});
}
在初始化列表中:
class Bar {
String a;
Bar(String b) : a = b;
}
您可以像这样在 null-safety 中创建您的 类
class JobDoc {
File? docCam1;
File? docCam2;
File? docBarcode;
File? docSignature;
JobDoc({this.docCam1, this.docCam2, this.docBarcode, this.docSignature});
JobDoc.fromJson(Map<String, dynamic> json) {
docCam1 = json['docCam1'] ?? null;
docCam2 = json['docCam2'] ?? null;
docBarcode = json['docBarcode'] ?? null;
docSignature = json['docSignature'] ?? null;
}
}
我已将我的 Dart 代码迁移到 NNBD / Null Safety。其中一些看起来像这样:
class Foo {
String? _a;
void foo() {
if (_a != null) {
_a += 'a';
}
}
}
class Bar {
Bar() {
_a = 'a';
}
String _a;
}
这会导致两个分析错误。对于 _a += 'a';
:
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.
对于Bar() {
:
Non-nullable instance field '_a' must be initialized. Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
在这两种情况下,我都完全按照错误提示做了!这是怎么回事?
我正在使用 Dart 2.12.0-133。2.beta(12 月 15 日星期二)。
编辑:我发现 this page 上面写着:
The analyzer can’t model the flow of your whole application, so it can’t predict the values of global variables or class fields.
但这对我来说没有意义 - 在这种情况下,从 if (_a != null)
到 _a += 'a';
只有一种可能的流量控制路径 - 没有异步代码并且 Dart 是单线程的 - 所以_a
不是本地的并不重要。
Bar()
的错误消息明确说明了在构造函数中初始化字段的可能性。
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 Foo {
String? _a;
void foo() {
final a = _a;
if (a != null) {
a += 'a';
_a = a;
}
}
}
虽然有点糟糕。我的代码现在充满了将 class 成员复制到局部变量并再次复制回来的代码。 :-/
Non-nullable instance field '_a' must be initialized. Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
啊原来“字段初始化器”实际上是这样的:
class Bar {
Bar() : _a = 'a';
String _a;
}
问题是 class 字段可以被覆盖,即使它被标记为 final
。下面的例子说明了这个问题:
class A {
final String? text = 'hello';
String? getText() {
if (text != null) {
return text;
} else {
return 'WAS NULL!';
}
}
}
class B extends A {
bool first = true;
@override
String? get text {
if (first) {
first = false;
return 'world';
} else {
return null;
}
}
}
void main() {
print(A().getText()); // hello
print(B().getText()); // null
}
B
class 覆盖 text
final 字段,因此它 return 是第一次被询问时的值,但 return 是 null
在此之后。您不能以可以阻止允许这种形式的覆盖的方式编写 A
class。
所以我们无法将 getText
的 return 值从 String?
更改为 String
,即使看起来我们检查了 text
字段中的 null
在 return 之前。
处理这种情况的方法很少。我给了一个
使用局部变量(推荐)
void foo() { var a = this.a; // <-- Local variable if (a != null) { a += 'a'; this.a = a; } }
使用??
void foo() { var a = (this.a ?? '') + 'a'; this.a = a; }
使用 Bang 运算符 (!)
只有当您 100% 确定变量 (
a
) 在您使用时不是null
时,才应使用此解决方案。void foo() { a = a! + 'a'; // <-- Bang operator }
回答你的第二个问题:
不可为 null 的字段应始终进行初始化。一般有3种初始化方式:
声明中:
class Bar { String a = 'a'; }
初始化中
class Bar { String a; Bar({required this.a}); }
在初始化列表中:
class Bar { String a; Bar(String b) : a = b; }
您可以像这样在 null-safety 中创建您的 类
class JobDoc {
File? docCam1;
File? docCam2;
File? docBarcode;
File? docSignature;
JobDoc({this.docCam1, this.docCam2, this.docBarcode, this.docSignature});
JobDoc.fromJson(Map<String, dynamic> json) {
docCam1 = json['docCam1'] ?? null;
docCam2 = json['docCam2'] ?? null;
docBarcode = json['docBarcode'] ?? null;
docSignature = json['docSignature'] ?? null;
}
}