在Dart泛型中应该优先使用dynamic还是Object?

引言

先看一段代码:

dart 复制代码
void main() {
  List a = [1, 2, 3];
}

你认为,此段代码中的变量a在运行时是什么类型的?

strict-raw-types

dart静态分析器有一个选项叫做strict-raw-types。先看看什么是strict-raw-types,也就是严格原始类型

本文件规定了通过静态分析选项启用的"严格原始类型"模式。作为一项静态分析选项,我们仅打算在Dart分析器中实现此功能。在此功能下,省略类型参数的类型被定义为"原始类型"。Dart会使用类型参数的边界来填充此类类型参数;若无边界,则使用dynamic类型填充。

以上是机翻,英语好的可以看看原文:

This document specifies the "Strict raw types" mode enabled with a static analysis option. As a static analysis option, we only intend to implement this feature in the Dart Analyzer. Under this feature, a type with omitted type argument(s) is defined as a "raw type." Dart fills in such type arguments with their bounds, or dynamic if there are no bounds.

在实际项目中,我也尝试去理解过,以此来确保我的代码严格遵守dart的类型系统。相信很多人也在网上学习过dart中的dynamicObject的区别,比如说:

  • Object是一个类,是所有类的基类,它提供了一些通用方法。需要显式调用方法来操作。
  • dynamic是一种特殊类型,用于表示动态类型,可以在运行时具有不同的类型值。
  • 使用Object通常是为了访问通用方法,而使用dynamic通常是为了处理动态的、不确定类型的情况。

其实dynamicObject 之间的微妙差异在`Dart 语言规范中有所体现,以下是最新的 Dart 语言规范第 20.7 节

dynamicObject? 之间存在一个微妙差异。根据最新的Dart 语言规范第 20.7 节

dynamic 类型是一种静态类型,和 Object 一样,它是所有其他类型的超类型。但它与其他类型的不同之处在于,静态分析会假定对其进行的每次成员访问,都存在一个具有能允许该访问的签名的对应成员。

同样的是机翻,说人话就是,dynamic其实和Object一样都是其他类型的超类,只是dynamic类型的变量在静态分析时,调用什么方法或者属性都可以,静态分析器都会假设该成员存在。看代码: void main(List args) { final d = []; final o = <Object?>[];

d.add(Object()); o.add(Object());

d[0].missingMethod(); // 会导致运行时的NoSuchMethodError. // NoSuchMethodError: Class 'Object' has no instance method 'missingMethod'.

o[0].missingMethod(); // 静态分析器直接报错.

}

以下是原文:

The type dynamic is a static type which is a supertype of all other types, just like Object, but it differs from other types in that the static analysis assumes that every member access has a corresponding member with a signature that admits the given access.

所以,dynmaic实际上是静态类型系统的"逃逸口",dynamic 允许开发者在编译期绕过类型检查,但必须在运行时承担类型不匹配的风险。过度使用会导致代码失去静态类型系统的保护。这也是为什么会有strict-raw-types这条原则的原因。

回到问题

回到文章开头的问题,我们也可以在规范中找到明确的答案:

If no static type annotation has been provided, the type system considers declarations to have type dynamic. If a generic type is used but type arguments are not provided, the type arguments default to type dynamic.This means that given a generic declaration G<P1, . . . , Pn>. . ., where Pi is a formal type parameter declaration, i ∈ 1..n, the type G is equivalent to G<dynamic, . . . , dynamic>.

翻译过来就是:

如果没有提供静态类型注解,类型系统会认为这些声明的类型为 dynamic。如果使用了泛型类型但没有提供类型参数,那么类型参数默认是 dynamic 类型。这意味着,对于一个泛型声明 G<P1, ..., Pn> ...,其中 Pi 是形式类型参数声明,i 的取值范围是从 1 到 n,那么类型 G 等同于 G<dynamic, ..., dynamic>。

所以回头再看这段代码:

dart 复制代码
void main() {
  List a = [1, 2, 3];
}

开发者常常认为类型推断是从赋值语句的右侧来确定变量a的类型。看起来变量a似乎是List<int>类型。但实际上,Dart会用 dynamic(或者对应类型参数的边界类型)来填充省略的类型参数,比如 List 中的 E。List a; 纯粹是 List<dynamic> a; 的简写形式。然后,类型推断是从变量a流向赋值语句右侧的表达式。这在另一个例子中会更加明显:

dart 复制代码
void main() {
  List a = [1, 2, 3]..forEach((e) => print(e.length));
  var b = [4, 5, 6]..forEach((e) => print(e.length));
}

第一条语句不会导致任何静态分析错误,因为列表的类型会被推断为List<dynamic>。相反,当对int类型调用length getter时,代码会在运行时抛出no-such-method

然而,第二条语句允许列表的类型从其元素推断为List<int>,这会导致静态分析类型错误,提示int上未定义length getter。

原始类型(Raw types)还可能导致意外的动态派发(dynamic dispatch):

dart 复制代码
void main() {
  List a = [1, 2, 3]; // 原始类型,等价于 List<dynamic>
  a.forEach((e) => print(e.isEven)); // 回调参数 e 的类型是 dynamic
}

开发者可能没有意识到,回调函数的参数edynamic类型,对isEven的调用属于动态派发(运行时才会检查方法是否存在)。

启用严格原始类型检查 旨在鼓励开发者填充省略的类型参数,避免默认使用dynamic。当唯一合适的类型确实是dynamic时,显式将其作为类型参数(如List<dynamic>)可以避免原始类型,同时让代码中的动态行为更加明确。

结束语

通过以上阅读,我们对strict-raw-types的初衷也有一定的了解,也知道了为什么会有这条原则,希望对日后的开发工作有所帮助。最后,附上如何开记strict-raw-types:

yaml 复制代码
analyzer:
  language:
    strict-raw-types: true

也希望大家关注我的微信公众号:OpenFlutter,感恩。

相关推荐
胡斌附体11 分钟前
vue添加loading后修复页面渲染问题
前端·javascript·vue.js·渲染·v-if·异步加载
酷爱码1 小时前
css中的 vertical-align与line-height作用详解
前端·css
沐土Arvin1 小时前
深入理解 requestIdleCallback:浏览器空闲时段的性能优化利器
开发语言·前端·javascript·设计模式·html
专注VB编程开发20年1 小时前
VB.NET关于接口实现与简化设计的分析,封装其他类
java·前端·数据库
小妖6661 小时前
css 中 content: “\e6d0“ 怎么变成图标的?
前端·css
L耀早睡2 小时前
mapreduce打包运行
大数据·前端·spark·mapreduce
HouGISer2 小时前
副业小程序YUERGS,从开发到变现
前端·小程序
outstanding木槿3 小时前
react中安装依赖时的问题 【集合】
前端·javascript·react.js·node.js
霸王蟹3 小时前
React中useState中更新是同步的还是异步的?
前端·javascript·笔记·学习·react.js·前端框架
霸王蟹3 小时前
React Hooks 必须在组件最顶层调用的原因解析
前端·javascript·笔记·学习·react.js