Dart null 安全性不适用于类字段
Posted
技术标签:
【中文标题】Dart null 安全性不适用于类字段【英文标题】:Dart null safety doesn't work with class fields [duplicate] 【发布时间】:2021-04-04 00:08:14 【问题描述】:我已将我的 Dart 代码迁移到 NNBD / Null Safety。其中一些看起来像这样:
class Foo
String? _a;
void foo()
if (_a != null)
_a += 'a';
class Bar
Bar()
_a = 'a';
String _a;
这会导致两个分析错误。对于_a += 'a';
:
值可以为“null”的表达式必须先进行空值检查,然后才能取消引用。 在取消引用之前尝试检查该值是否不是“空”。
对于Bar()
:
必须初始化不可为空的实例字段“_a”。 尝试添加初始值设定项表达式,或在此构造函数中添加字段初始值设定项,或将其标记为“延迟”。
在这两种情况下,我已经完全按照错误提示进行了操作!这是怎么回事?
我正在使用 Dart 2.12.0-133.2.beta(12 月 15 日星期二)。
编辑:我找到了this page,上面写着:
分析器无法对整个应用程序的流程进行建模,因此无法预测全局变量或类字段的值。
但这对我来说没有意义 - 在这种情况下,从 if (_a != null)
到 _a += 'a';
只有一个可能的流控制路径 - 没有异步代码并且 Dart 是单线程的 - 所以没关系_a
不是本地的。
Bar()
的错误消息明确指出了在构造函数中初始化字段的可能性。
【问题讨论】:
这能回答你的问题吗? Null check doesn't cause type promotion in Dart 【参考方案1】:问题是类字段即使被标记为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
类覆盖 text
最终字段,因此它在第一次被询问时返回一个值,但在此之后返回 null
。您不能以可以防止允许这种形式的覆盖的方式编写您的 A
类。
所以我们不能将getText
的返回值从String?
更改为String
,即使看起来我们在返回之前检查了text
字段中的null
。
【讨论】:
我觉得奇怪的是,一个相当深奥的用例,比如在子类中覆盖 final,现在在 NNBD 实现中出现了巨大的缺陷。 Flutter 布局将充满!
,每一个都会在未来产生潜在的错误,因为替代方案太冗长了。
@shawnblais 或者创建对成员值的本地引用。
谢谢@jamesdlin 我相信这实际上是唯一安全的方法。但我认为大多数开发人员只会使用 !运算符,然后基本上消除了 NNBD 对该变量的任何优势,并创建了将来很容易被破坏的脆弱方法。例如; if(index == null) return; index = index! + 1;
如果后来有人删除了这个空检查,编译器什么也没说,手榴弹就在那里。在这个看起来有点傻的例子中,但是对于超过几行的方法,这是一个重要的问题,那些!
运算符并没有完全跳到读者身上。
@Adnan 没错。 getter/setter 方法的目的是它们不与普通变量区分开来。从某种意义上说,final
变量是一个只有 getter 的字段,而var
变量既有 setter 又有 getter。这也意味着我们总是可以用新的例如覆盖一个变量。 getter 像我的例子,这是这个问题的核心。 (但这也是我们不创建像 Java 这样的 getSomethig()
和 setSomehing()
方法的原因,因为如果需要,我们可以随时将逻辑引入现有字段,而无需更改 API。
@Adnan 在 Java 中,很少公开类变量是标准做法。相反,我们定义getVariable()
和setVariable()
来获取和设置一个私有变量。原因是 Java 没有 getter/setter 方法的概念(如 Dart 或 C#),所以如果我们稍后要引入例如字段验证,如果不更改 API,我们就无法做到这一点,除非我们从一开始就使用一种方法来访问变量。【参考方案2】:
值可以为“null”的表达式必须先进行空值检查,然后才能取消引用。在取消引用之前尝试检查该值是否为“null”。
这似乎真的只适用于局部变量。此代码没有错误:
class Foo
String? _a;
void foo()
final a = _a;
if (a != null)
a += 'a';
_a = a;
虽然有点烂。我的代码现在充满了仅将类成员复制到局部变量并再次返回的代码。 :-/
必须初始化不可为空的实例字段“_a”。尝试添加初始化表达式,或在此构造函数中添加字段初始化,或将其标记为“延迟”。
啊,原来“字段初始化器”实际上是这样的:
class Bar
Bar() : _a = 'a';
String _a;
【讨论】:
【参考方案3】:处理这种情况的方法很少。我已经给出了detailed answer here,所以我只是从中编写解决方案:
使用局部变量(推荐)
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
回答你的第二个问题:
应始终初始化不可为空的字段。一般有三种初始化方式:
在声明中:
class Bar
String a = 'a';
在初始化形式中
class Bar
String a;
Bar(required this.a);
在初始化列表中:
class Bar
String a;
Bar(String b) : a = b;
【讨论】:
【参考方案4】:您可以像这样在 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 null 安全性不适用于类字段的主要内容,如果未能解决你的问题,请参考以下文章
Dart null 安全性和使用 json_serializable 将 JSON 解析为模型