Contents

The Dart type system

Dart语言具有类型安全性:它结合使用了静态类型检查和运行时检查,以确保变量的值始终与变量的静态类型匹配,有时也称为声音类型. 尽管类型是强制性的,但由于类型推断 ,类型注释是可选的.

有关Dart语言的完整介绍,包括类型,请参见语言导览 .

静态类型检查的一个好处是能够使用Dart的静态分析器在编译时发现错误.

您可以通过将类型注释添加到泛型类来修复大多数静态分析错误. 最常见的通用类是集合类型List<T>Map<K,V> .

例如,在下面的代码中, printInts()函数打印一个整数列表,而main()创建一个列表并将其传递给printInts() .

void printInts(List<int> a) => print(a);

void main() {
  var list = [];
  list.add(1);
  list.add("2");
  printInts(list);
}

的前面的代码导致在一个类型的错误list在该呼叫(以上强调) printInts(list)

error • The argument type 'List<dynamic>' can't be assigned to the parameter type 'List<int>' • argument_type_not_assignable

该错误突出显示了从List<dynamic>List<int>的不正确的隐式转换. list变量具有静态类型List<dynamic> . 这是因为初始化声明var list = []没有为分析器提供足够的信息来推断类型参数而不是dynamic . printInts()函数需要一个类型为List<int>的参数,从而导致类型不匹配.

在创建列表时添加类型注释( <int> )(下面突出显示)时,分析器会抱怨无法将字符串参数分配给int参数. 删除list.add("2")的引号会导致代码通过静态分析,并且运行时没有错误或警告.

void printInts(List<int> a) => print(a);

void main() {
  var list = <int>[];
  list.add(1);
  list.add(2);
  printInts(list);
}

Try it in DartPad.

What is soundness?

健全性是关于确保您的程序不会进入某些无效状态. 声音类型系统意味着您永远无法进入表达式计算得出的值与表达式的静态类型不匹配的状态. 例如,如果表达式的静态类型为String ,则在运行时保证只在评估它时才得到一个字符串.

与Java和C#中的类型系统一样,Dart的类型系统也很完善. 它通过结合使用静态检查(编译时错误)和运行时检查来增强这种可靠性. 例如,将String分配给int是编译时错误. 如果对象不是字符串,则使用as StringObject转换as String失败,并出现运行时错误.

The benefits of soundness

声音类型系统具有以下优点:

  • 在编译时显示与类型相关的错误.
    声音类型系统会强制代码对其类型明确,因此在编译时会发现在运行时很难发现的与类型相关的错误.

  • 更具可读性的代码.
    代码更易于阅读,因为您可以依赖实际上具有指定类型的值. 在Sound Dart中,类型不会说谎.

  • 更具可维护性的代码.
    使用声音类型系统,当您更改一段代码时,类型系统可以警告您其他刚刚中断的代码.

  • 更好的提前(AOT)编译.
    尽管没有类型也可以进行AOT编译,但是生成的代码效率要低得多.

Tips for passing static analysis

静态类型的大多数规则很容易理解. 以下是一些不太明显的规则:

  • 覆盖方法时,请使用声音返回类型.
  • 覆盖方法时,请使用声音参数类型.
  • 不要将动态列表用作类型列表.

让我们使用使用以下类型层次结构的示例详细查看这些规则:

a hierarchy of animals where the supertype is Animal and the subtypes are Alligator, Cat, and HoneyBadger. Cat has the subtypes of Lion and MaineCoon

Use sound return types when overriding methods

子类中方法的返回类型必须与父类中方法的返回类型相同或相同. 考虑Animal类中的getter方法:

class Animal {
  void chase(Animal a) { ... }
  Animal get parent => ...
}

parent getter方法返回Animal. 在HoneyBadger子类中,可以用HoneyBadger(或Animal的任何其他子类型)替换getter的返回类型,但是不允许使用不相关的类型.

class HoneyBadger extends Animal {
  void chase(Animal a) { ... }
  HoneyBadger get parent => ...
}
class HoneyBadger extends Animal {
  void chase(Animal a) { ... }
  Root get parent => ...
}

Use sound parameter types when overriding methods

重写方法的参数必须与超类中的相应参数具有相同的类型或超类型. 不要通过用原始参数的子类型替换参数类型来"拧紧"参数类型.

考虑对Animal类的chase(Animal)方法:

class Animal {
  void chase(Animal a) { ... }
  Animal get parent => ...
}

chase()方法采用动物. HoneyBadger追逐任何东西. 可以重写chase()方法以获取任何内容(对象)是可以的.

class HoneyBadger extends Animal {
  void chase(Object a) { ... }
  Animal get parent => ...
}

下面的代码将动物chase() Animal chase()的子类Mouse chase()动物chase()chase()方法上的参数收紧.

class Mouse extends Animal {...}

class Cat extends Animal {
  void chase(Mouse x) { ... }
}

这段代码的类型不安全,因为这样就可以定义一只猫,并将其发送到鳄鱼后:

Animal a = Cat();
a.chase(Alligator()); // Not type safe or feline safe

Don’t use a dynamic list as a typed list

当您想要一个包含不同种类的东西的列表时,动态列表会很好. 但是,您不能将动态列表用作类型列表.

此规则也适用于泛型类型的实例.

以下代码创建一个动态的Dog列表,并将其分配给Cat类型的列表,该列表在静态分析期间产生错误.

class Cat extends Animal { ... }

class Dog extends Animal { ... }

void main() {
  List<Cat> foo = <dynamic>[Dog()]; // Error
  List<dynamic> bar = <dynamic>[Dog(), Cat()]; // OK
}

Runtime checks

Dart VMdartdevc等工具中的运行时检查处理分析器无法捕获的类型安全性问题.

例如,以下代码在运行时引发异常,因为将"狗"列表分配给"猫"列表是错误的:

void main() {
  List<Animal> animals = [Dog()];
  List<Cat> cats = animals;
}

Type inference

分析器可以推断字段,方法,局部变量和大多数通用类型参数的类型. 当分析器没有足够的信息来推断特定类型时,它将使用dynamic类型.

这是类型推断如何与泛型一起工作的示例. 在此示例中,名为arguments包含一个映射,该映射将字符串键与各种类型的值配对.

如果您明确键入变量,则可以这样编写:

Map<String, dynamic> arguments = {'argA': 'hello', 'argB': 42};

另外,您可以使用var并让Dart推断类型:

var arguments = {'argA': 'hello', 'argB': 42}; // Map<String, Object>

地图文字从其条目推断其类型,然后变量从地图文字的类型推断其类型. 在此映射中,键都是字符串,但是值具有不同的类型(String和int,具有上限对象). 因此,地图文字的类型为Map<String, Object> ,而arguments变量也是如此.

Field and method inference

没有指定类型并且从超类覆盖该字段或方法的字段或方法将继承超类方法或字段的类型.

没有声明或继承的类型但具有初始值声明的字段将基于初始值获得推断的类型.

Static field inference

静态字段和变量从其初始值设定项中推断出其类型. 请注意,如果遇到一个循环,则推理将失败(也就是说,为变量推断类型取决于了解该变量的类型).

Local variable inference

局部变量类型从其初始值设定项(如果有)中推断出来. 后续分配将不考虑在内. 这可能意味着可能会推断出过于精确的类型. 如果是这样,您可以添加类型注释.

var x = 3; // x is inferred as an int
x = 4.0;
num y = 3; // a num can be double or int
y = 4.0;

Type argument inference

Type arguments to constructor calls and generic method invocations are inferred based on a combination of downward information from the context of occurrence, and upward information from the arguments to the constructor or generic method. If inference is not doing what you want or expect, you can always explicitly specify the type arguments.

// Inferred as if you wrote <int>[].
List<int> listOfInt = [];

// Inferred as if you wrote <double>[3.0].
var listOfDouble = [3.0];

// Inferred as Iterable<int>
var ints = listOfDouble.map((x) => x.toInt());

在最后一个示例中,使用向下信息将x推断为double . 使用向上信息将闭包的返回类型推断为int . 当推断map()方法的类型参数: <int>时,Dart使用此返回类型作为向上信息.

Substituting types

当您覆盖某个方法时,您将用一种可能具有新类型的东西(在新方法中)替换一种类型的东西(在旧方法中). 同样,当您将参数传递给函数时,您将用一种类型(实际参数)替换具有一种类型的参数(具有声明类型的参数). 什么时候可以用具有子类型或超类型的东西替换具有一种类型的东西?

在替换类型时,有助于从消费者生产者的角度进行思考. 消费者吸收类型,生产者产生类型.

您可以将消费者的类型替换为超类型,将生产者的类型替换为子类型.

让我们看一下简单类型分配和泛型类型分配的示例.

Simple type assignment

When assigning objects to objects, when can you replace a type with a different type? The answer depends on whether the object is a consumer or a producer.

考虑以下类型层次结构:

a hierarchy of animals where the supertype is Animal and the subtypes are Alligator, Cat, and HoneyBadger. Cat has the subtypes of Lion and MaineCoon

考虑以下简单分配,其中Cat c消费者,Cat()生产者

Cat c = Cat();

在使用位置,将消耗特定类型的东西( Cat )替换为使用任何东西的东西( Animal )是安全的,因此允许使用Animal c替换Cat c ,因为Animal是Cat的超类型.

Animal c = Cat();

但是用MaineCoon c替换Cat c会破坏类型安全性,因为超类可能会提供具有不同行为的Cat类型,例如Lion:

MaineCoon c = Cat();

在生产位置,可以用更具体的类型(MaineCoon)替换产生类型(Cat)的东西. 因此,允许以下操作:

Cat c = MaineCoon();

Generic type assignment

通用类型的规则是否相同? 是. 考虑一下动物列表的层次结构-Cat列表是Animal列表的子类型,而MaineCoon列表的超类型是:

List<Animal> -> List<Cat> -> List<MaineCoon>

在以下示例中,可以将MaineCoon列表分配给myCats因为List<MaineCoon>List<Cat>的子类型:

List<Cat> myCats = List<MaineCoon>();

朝另一个方向去怎么办? 您可以将Animal列表分配给List<Cat>吗?

List<Cat> myCats = List<Animal>();

此分配通过静态分析,但创建了隐式强制转换. 它等效于:

List<Cat> myCats = List<Animal>() as List<Cat>;

该代码可能在运行时失败. 您可以通过在分析选项文件中指定implicit-casts: false来禁止隐式转换.

Methods

覆盖方法时,生产者规则和消费者规则仍然适用. 例如:

Animal class showing the chase method as the consumer and the parent getter as the producer

对于使用者(例如chase(Animal)方法),可以将参数类型替换为超类型. 对于生产者(例如parent getter方法),可以将返回类型替换为子类型.

有关更多信息,请参见在重写方法时使用声音返回类型和在重写方法时 使用声音参数类型 .

Other resources

以下资源具有有关Dart的更多信息:

by  ICOPY.SITE