- 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语言中的注解。
Table of contents
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, @override
the annotation is a built-in annotation indicating that the method of the Man class speak
overrides 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
@override
The 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
@deprecated
Annotations 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 oldMethod
mark the method as deprecated and recommend it newMethod
. oldMethod
The Dart analyzer will warn you when a developer tries to use it .
3. Some common annotations provided by third-party modules
2.3 @required
@required
Annotations 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 MyClass
class with one required named parameter name
. We use annotations on constructor parameters @required
to indicate this. Note that we need to package:meta/meta.dart
import annotations from the library @required
.
2.4 @sealed
@sealed
Annotations 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 MyBaseClass
sealed class named , and create a subclass named MyDerivedClass
. In another library, we tried to create a AnotherDerivedClass
subclass called , but this is not allowed because MyBaseClass
it is sealed. Note that we need to package:meta/meta.dart
import annotations from the library @sealed
.
2.6 @protected
@protected
Annotations 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 MyBaseClass
which has a protected method protectedMethod()
. We use @protected
the annotation to indicate that this method can only MyBaseClass
be used in and its subclasses. Then, we create a MyBaseClass
subclass that inherits from MyDerivedClass
and call the protected method in it.
Note that if we try to MyDerivedClass
call 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
@immutable
Used to mark a class as immutable (Immutable). An immutable class is a class whose instances cannot be modified after creation , all fields are final
unique, and there are no public mutable methods . Such classes can provide stronger security and thread safety.
Using @immutable
annotations 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, name
and age
the fields are final
all , 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
@alwaysThrows
The 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 throwError
function called that takes a string argument and throws an exception. We use @alwaysThrows
the annotation to indicate that this function always throws an exception. Then, we foo
use throwError
the function inside the function. Because of our use of @alwaysThrows
the annotation, the Dart static analyzer correctly recognizes that in the branch foo
of the function else
, throwError
the function does not return a value.
Using @alwaysThrows
annotations 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 Annotation
are 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 const
class 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 MyAnnotation
custom annotation called . This annotation has a property description
that stores the textual description associated with the annotation. We also provide a const
constructor 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 const
the 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 MyAnnotation
annotate 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 const
the constructor to create the annotation instance.
3.3 Read custom annotations
To read custom annotations, you can use reflection ( dart:mirrors
library) or code generation tools (like source_gen
library). 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:mirrors
in the library reflectClass
to get MyClass
it ClassMirror
. Then, we iterate over ClassMirror
the metadata
properties, checking whether each metadata item is MyAnnotation
a type. If a matching annotation is found, we can fetch its description
attributes and output it.
Note that since dart:mirrors
libraries are not supported in Flutter, code generation tools such as libraries are required in Flutter applications source_gen
to 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 MyAnnotation
that 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:mirrors
libraries) or code generation tools (like source_gen
libraries).
class MyAnnotation {
final String description;
const MyAnnotation(this.description);
// 错误示例:试图直接访问被注解对象的属性或方法
void printDescription(Object annotatedObject) {
print(annotatedObject.description); // 错误:不能直接访问被注解对象的属性
}
}
In the above example, we tried to MyAnnotation
define a method in the class printDescription
which tried to directly access description
the 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 MyClass
added an MyAnnotation
annotation 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.