Dart Notes: Annotations (metadata) in the Dart language

Dart、Flutter
Annotations in the Dart language

- Article information -
Author: Jack Lee (jcLee95)
Visit me at: https://jclee95.blog.csdn.net
Email: [email protected].
Shenzhen China
Address of this article:https://blog.csdn.net/qq_28550263/article/details/131384014

【介绍】:本文讲解 Dart语言中的注解。


1. The role of annotations

Annotations , also known as metadata , are used to add additional information to the code to describe the code. Annotations can be applied to classes , methods , properties , constructors , function parameters , etc.

The role of annotations is to provide a method for obtaining additional information about the program structure at compile time or runtime , so that more advanced programming techniques can be implemented, such as automatic serialization and deserialization, dependency injection, etc.

Annotations can be used to add additional information to the code. It does not affect the execution of the program itself, but they can be read by the compiler, static analysis tools, or runtime reflection API. These tools can perform specific operations based on the information provided by the annotations, such as:

  • generate extra code
  • Check the correctness of the code
  • modify the behavior of the program

2. Usage of annotations

The grammatical form of the annotation is: @Annotation, which can be placed directly in front of the code that needs to be annotated. A code element can have multiple annotations, separated by commas. For example:

class Person {
    
    
  void speak() {
    
    
    print('你好!');
  }
}

class Man extends Person {
    
    
  
  void speak() {
    
    
    print('你好,世界!');
  }
}

In this example, @overridethe annotation is a built-in annotation indicating that the method of the Man class speakoverrides the method of the Person class speak. Can help the compiler to check whether the method of the parent class is correctly overridden. This built-in annotation will be described in further detail in the next section.

2. Common built-in annotations

Like the annotations used in the previous section @override, Dart provides some built-in annotations that can be used directly in the code. There are currently three built-in annotations in the Dart language - @Deprecated, @deprecated, and @override

built-in annotation describe
@Deprecated Used to create a stale annotation with a custom message and expiration time.
@deprecated Indicates that an API is outdated and is not recommended for use. Static analysis tools give warnings when outdated APIs are used.
@override Indicates that the method of the subclass overrides the method of the superclass. Static analysis tools will give warnings if not covered correctly.

Next, we will analyze more common built-in annotations one by one.

2.1 @override

@overrideThe annotation is used to indicate that the method of the subclass overrides the method of the parent class. This helps ensure that superclass methods are correctly overridden when refactoring code. The Dart analyzer will issue a warning if it is not covered correctly.

An example of this annotation has already been given and will not be repeated here.

2.2 @Deprecated 和 @deprecated

@deprecatedAnnotations are used to indicate that a class, method or property is deprecated and its use is not recommended. This helps communicate to developers which features have been deprecated when upgrading a library or framework. The Dart analyzer issues warnings when deprecated features are used.

For example:

class MyClass {
    
    
  
  void oldMethod() {
    
    
    print('This method is deprecated');
  }

  void newMethod() {
    
    
    print('Use this method instead');
  }
}

In this example, we oldMethodmark the method as deprecated and recommend it newMethod. oldMethodThe Dart analyzer will warn you when a developer tries to use it .

3. Some common annotations provided by third-party modules

2.3 @required

@requiredAnnotations are used to indicate that named parameters of a constructor or method are required. This helps ensure that required parameters are provided when calling a constructor or method. The Dart analyzer issues a warning if a required parameter is missing.

For example:

import 'package:meta/meta.dart';

class Person {
    
    
  final String name;

  MyClass({
    
     this.name}) {
    
    
    if (name == null) {
    
    
      throw ArgumentError('name is required');
    }
  }
}

void main() {
    
    
  MyClass obj = Person(name: 'Jack'); // 正确使用
  MyClass obj2 = Person();            // 分析器会发出警告,缺少必需的参数
}

In this example, we create a MyClassclass with one required named parameter name. We use annotations on constructor parameters @requiredto indicate this. Note that we need to package:meta/meta.dartimport annotations from the library @required.

2.4 @sealed

@sealedAnnotations are used to indicate that a class is sealed, i.e. it can only be extended in the library in which it is defined. This helps limit class inheritance, ensuring that the library's internal implementation cannot be broken by external code.

For example:

// 在my_library.dart文件中
import 'package:meta/meta.dart';


class MyBaseClass {
    
    
  void myMethod() {
    
    
    print('This is a sealed class');
  }
}

class MyDerivedClass extends MyBaseClass {
    
    
  // 这是允许的,因为MyDerivedClass和MyBaseClass在同一个库中
}

// 在main.dart文件中
import 'my_library.dart';

class AnotherDerivedClass extends MyBaseClass {
    
    
  // 这是不允许的,因为AnotherDerivedClass和MyBaseClass在不同的库中
}

In this example, we create a MyBaseClasssealed class named , and create a subclass named MyDerivedClass. In another library, we tried to create a AnotherDerivedClasssubclass called , but this is not allowed because MyBaseClassit is sealed. Note that we need to package:meta/meta.dartimport annotations from the library @sealed.

2.6 @protected

@protectedAnnotations are used to indicate that a class member (method, property, etc.) can only be accessed by its subclasses. This helps with encapsulation, ensuring that a class's internal implementation cannot be misused by other classes.

For example:

import 'package:flutter/foundation.dart';

class MyBaseClass {
    
    
  
  void protectedMethod() {
    
    
    print('This is a protected method.');
  }
}

class MyDerivedClass extends MyBaseClass {
    
    
  void callProtectedMethod() {
    
    
    // 可以在子类中访问受保护的方法
    protectedMethod();
  }
}

void main() {
    
    
  MyDerivedClass derivedClass = MyDerivedClass();
  derivedClass.callProtectedMethod(); // 输出 "This is a protected method."
}

In this example, we define a base class MyBaseClasswhich has a protected method protectedMethod(). We use @protectedthe annotation to indicate that this method can only MyBaseClassbe used in and its subclasses. Then, we create a MyBaseClasssubclass that inherits from MyDerivedClassand call the protected method in it.

Note that if we try to MyDerivedClasscall it in a class other than protectedMethod(), the Dart analyzer will give a warning that we should not use protected members outside of subclasses.

2.5 @immutable

@immutableUsed to mark a class as immutable (Immutable). An immutable class is a class whose instances cannot be modified after creation , all fields are finalunique, and there are no public mutable methods . Such classes can provide stronger security and thread safety.

Using @immutableannotations allows the compiler to check the immutability of the class and prevent it from being modified. If you try to modify the value of a field or add a mutable method in an immutable class, the compiler will generate a warning or error.


class Person {
    
    
  final String name;
  final int age;

  Person(this.name, this.age);
}

In the above example, the Person class is marked as immutable, nameand agethe fields are finalall , and have no public mutable methods. Once an instance of the Person class is created , the values ​​of its fields cannot be changed. The benefits of using immutable classes are:

  • Thread Safety: Immutable objects do not need to worry about concurrent modification and can be safely shared in a multi-threaded environment.
  • Predictable: Since the value of an immutable object does not change, the state consistency of the object can be guaranteed at any time.
  • Code simplification: Immutable objects reduce the chance of errors because there is no possibility to modify the state.

It should be noted that the @immutable annotation is only a kind of metadata, and it does not enforce the immutability of the class by itself. It is mainly used by static analysis tools and code generation tools to check and generate related code.

2.7 @alwaysThrows

@alwaysThrowsThe annotation indicates that a function always throws an exception. This helps static analysis tools understand the behavior of the code so they can better catch potential bugs.

For example:

import 'package:meta/meta.dart';


void throwError(String message) {
    
    
  throw Exception(message);
}

int foo(bool condition) {
    
    
  if (condition) {
    
    
    return 1;
  } else {
    
    
    throwError("Error occurred");
  }
}

void main() {
    
    
  try {
    
    
    print(foo(false));
  } catch (e) {
    
    
    print(e);  // 输出 "Exception: Error occurred"
  }
}

In this example, we define a throwErrorfunction called that takes a string argument and throws an exception. We use @alwaysThrowsthe annotation to indicate that this function always throws an exception. Then, we foouse throwErrorthe function inside the function. Because of our use of @alwaysThrowsthe annotation, the Dart static analyzer correctly recognizes that in the branch fooof the function else, throwErrorthe function does not return a value.

Using @alwaysThrowsannotations can help the static analyzer analyze the code more accurately, thereby improving the readability and maintainability of the code.

3. Custom annotations

3.1 Define custom annotation syntax (how to define)

In addition to using built-in annotations, we can also customize annotations. Custom annotations need to create a class and inherit from it Object. Class names Annotationare suffixed. Custom annotation classes can contain any number of parameters that can be passed in when using the annotation. To define custom annotations, you need to create a constclass with a constructor. This class can have any number of properties, but they must be compile-time constants.

For example:

class MyAnnotation {
    
    
  final String description;
  const MyAnnotation(this.description);
}

In this example, we define a MyAnnotationcustom annotation called . This annotation has a property descriptionthat stores the textual description associated with the annotation. We also provide a constconstructor for this class so that we can create compile-time constants when using annotations.

3.2 Custom annotation usage (how to use)

@To use a custom annotation, simply add a symbol before the code element to be annotated , followed by the name of the annotation and constthe call to the constructor. For example, custom annotations can be applied to classes, functions, variables, etc.:

// 使用自定义注解注解类
('这是一个注解类的元数据')
class MyClass {
    
    
  // ...
}

// 使用自定义注解注解函数
('这是一个注解函数的元数据')
void myFunction() {
    
    
  // ...
}

// 使用自定义元素据注解变量
('这是一个注解变量的元数据')
int myVariable;

In this example, we MyAnnotationannotate a class, a function, and a variable with annotations. Note that the value of the annotation must be a compile-time constant, so we use constthe constructor to create the annotation instance.

3.3 Read custom annotations

To read custom annotations, you can use reflection ( dart:mirrorslibrary) or code generation tools (like source_genlibrary). Here is an example of using reflection to read custom annotations:

import 'dart:mirrors';

void main() {
    
    
  // 获取MyClass的ClassMirror
  ClassMirror classMirror = reflectClass(MyClass);

  // 遍历MyClass的元数据
  for (var metadata in classMirror.metadata) {
    
    
    // 检查元数据是否为MyAnnotation类型
    if (metadata.reflectee is MyAnnotation) {
    
    
      // 获取MyAnnotation实例
      MyAnnotation annotation = metadata.reflectee;

      // 输出注解的描述
      print('MyClass annotation: ${
      
      annotation.description}');
    }
  }
}

In this example, we use a function dart:mirrorsin the library reflectClassto get MyClassit ClassMirror. Then, we iterate over ClassMirrorthe metadataproperties, checking whether each metadata item is MyAnnotationa type. If a matching annotation is found, we can fetch its descriptionattributes and output it.

Note that since dart:mirrorslibraries are not supported in Flutter, code generation tools such as libraries are required in Flutter applications source_gento read and process custom annotations.

4 Some notes on using annotations

4.1 Annotations must be constant expressions

The value of the annotation must be a compile-time constant expression. This means that annotations cannot contain values ​​computed at runtime, such as variables, non-constant function calls, etc. The constant expression requirement ensures that annotations can be resolved at compile time, allowing them to be used for compiler optimization, static analysis, etc.

// 示例:定义一个简单的自定义注解
class MyAnnotation {
    
    
  final String description;
  const MyAnnotation(this.description);
}

// 使用自定义注解(正确示例)
('This is a constant description')
class MyClass {
    
    
  // ...
}

// 使用自定义注解(错误示例)
String runtimeDescription = 'This is a runtime description';

(runtimeDescription) // 错误:注解必须是常量表达式
class MyClass2 {
    
    
  // ...
}

In the example above, we defined a simple custom annotation MyAnnotationthat takes a string parameter. When using this annotation, we need to provide a constant expression as a parameter. If we try to use a runtime computed value (such as a variable runtimeDescription), it will cause a compilation error.

4.2 Annotations cannot directly access the properties or methods of the annotated object

The annotation itself cannot directly access or modify the properties or methods of the annotated object. To achieve this, we need to resort to reflection ( dart:mirrorslibraries) or code generation tools (like source_genlibraries).

class MyAnnotation {
    
    
  final String description;
  const MyAnnotation(this.description);

  // 错误示例:试图直接访问被注解对象的属性或方法
  void printDescription(Object annotatedObject) {
    
    
    print(annotatedObject.description); // 错误:不能直接访问被注解对象的属性
  }
}

In the above example, we tried to MyAnnotationdefine a method in the class printDescriptionwhich tried to directly access descriptionthe property of the annotated object. This is not allowed because annotations cannot directly access properties or methods of the annotated object.

4.3 Annotations do not affect program execution

The annotation itself does not affect the execution of the program. They are provided only as metadata to compilers, static analysis tools, or the runtime reflection API. To use annotations to achieve certain functions, we need to use these tools to read and process annotations.

('This is a description')
class MyClass {
    
    
  // ...
}

void main() {
    
    
  // 注解不会影响程序的执行
  MyClass myObject = MyClass();
  // ...
}

In the above example, we MyClassadded an MyAnnotationannotation to the class. However, this annotation does not affect the execution of the program, nor does it automatically trigger any actions. To take advantage of this annotation, we need to use corresponding tools (such as reflection or code generation) to read and process it.

Guess you like

Origin blog.csdn.net/qq_28550263/article/details/131384014