注释¶
注释可用于向源代码中的某些功能添加元数据。类型、方法、实例变量以及方法/宏参数可以进行注释。用户定义的注释(如标准库中的 JSON::Field)使用 annotation
关键字定义。编译器提供了许多 内置注释。
用户可以使用 annotation
关键字定义自己的注释,其工作方式类似于定义 class
或 struct
。
annotation MyAnnotation
end
然后可以将注释应用于各种项目,包括
- 实例和类方法
- 实例变量
- 类、结构体、枚举和模块
- 方法和宏参数(尽管后者目前无法访问)
annotation MyAnnotation
end
@[MyAnnotation]
def foo
"foo"
end
@[MyAnnotation]
class Klass
end
@[MyAnnotation]
module MyModule
end
def method1(@[MyAnnotation] foo)
end
def method2(
@[MyAnnotation]
bar
)
end
def method3(@[MyAnnotation] & : String ->)
end
应用¶
注释最适合用于存储有关给定实例变量、类型或方法的元数据,以便可以使用宏在编译时读取这些元数据。注释的主要优势之一是它们直接应用于实例变量/方法,这使得类看起来更自然,因为不需要标准宏来创建这些属性/方法。
注释的一些应用
对象序列化¶
有一个注释,当应用于实例变量时,会确定是否应该序列化该实例变量,或者使用什么键。Crystal 的 JSON::Serializable
和 YAML::Serializable
就是这方面的例子。
ORM¶
注释可用于将属性指定为 ORM 列。除了注释之外,还可以从 TypeNode
中读取实例变量的名称和类型;从而无需任何 ORM 特定的宏。注释本身也可用于存储有关列的元数据,例如它是否可为空、列的名称或它是否是主键。
字段¶
数据可以存储在注释中。
annotation MyAnnotaion
end
# The fields can either be a key/value pair
@[MyAnnotation(key: "value", value: 123)]
# Or positional
@[MyAnnotation("foo", 123, false)]
键值¶
可以使用 []
方法在编译时访问注释键值对的值。
annotation MyAnnotation
end
@[MyAnnotation(value: 2)]
def annotation_value
# The name can be a `String`, `Symbol`, or `MacroId`
{{ @def.annotation(MyAnnotation)[:value] }}
end
annotation_value # => 2
named_args
方法可用于将应用于注释的所有键值对读取为 NamedTupleLiteral
。此方法默认情况下在所有注释上定义,并且对于每个应用的注释都是唯一的。
annotation MyAnnotation
end
@[MyAnnotation(value: 2, name: "Jim")]
def annotation_named_args
{{ @def.annotation(MyAnnotation).named_args }}
end
annotation_named_args # => {value: 2, name: "Jim"}
由于此方法返回 NamedTupleLiteral
,因此所有 方法 都可以在该类型上使用。特别是 #double_splat
,这使得将注释参数传递给方法变得容易。
annotation MyAnnotation
end
class SomeClass
def initialize(@value : Int32, @name : String); end
end
@[MyAnnotation(value: 2, name: "Jim")]
def new_test
{% begin %}
SomeClass.new {{ @def.annotation(MyAnnotation).named_args.double_splat }}
{% end %}
end
new_test # => #<SomeClass:0x5621a19ddf00 @name="Jim", @value=2>
位置¶
可以使用 []
方法在编译时访问位置值;但是,一次只能访问一个索引。
annotation MyAnnotation
end
@[MyAnnotation(1, 2, 3, 4)]
def annotation_read
{% for idx in [0, 1, 2, 3, 4] %}
{% value = @def.annotation(MyAnnotation)[idx] %}
pp "{{ idx }} = {{ value }}"
{% end %}
end
annotation_read
# Which would print
"0 = 1"
"1 = 2"
"2 = 3"
"3 = 4"
"4 = nil"
args
方法可用于将应用于注释的所有位置参数读取为 TupleLiteral
。此方法默认情况下在所有注释上定义,并且对于每个应用的注释都是唯一的。
annotation MyAnnotation
end
@[MyAnnotation(1, 2, 3, 4)]
def annotation_args
{{ @def.annotation(MyAnnotation).args }}
end
annotation_args # => {1, 2, 3, 4}
由于 TupleLiteral
的返回类型是可迭代的,因此我们可以以更好的方式重写前面的示例。通过扩展,所有 方法 都可以在 TupleLiteral
上使用。
annotation MyAnnotation
end
@[MyAnnotation(1, "foo", true, 17.0)]
def annotation_read
{% for value, idx in @def.annotation(MyAnnotation).args %}
pp "{{ idx }} = #{{{ value }}}"
{% end %}
end
annotation_read
# Which would print
"0 = 1"
"1 = foo"
"2 = true"
"3 = 17.0"
读取¶
可以使用 .annotation(type : TypeNode)
方法从 TypeNode
、Def
、MetaVar
或 Arg
中读取注释。此方法返回一个 Annotation
对象,表示应用于提供的类型的注释。
注意
如果应用了相同类型的多个注释,则 .annotation
方法将返回最后一个注释。
可以使用 @type
和 @def
变量来获取 TypeNode
或 Def
对象,以便在其上使用 .annotation
方法。但是,也可以使用 TypeNode
上的其他方法来获取 TypeNode
/Def
类型。例如,分别使用 TypeNode.all_subclasses
或 TypeNode.methods
。
提示
查看 parse_type
方法,了解获取 TypeNode
的更高级方法。
TypeNode.instance_vars
可用于获取实例变量 MetaVar
对象数组,这些对象可以用于读取在这些实例变量上定义的注释。
注意
TypeNode.instance_vars
目前仅在实例/类方法的上下文中有效。
annotation MyClass
end
annotation MyMethod
end
annotation MyIvar
end
annotation MyParameter
end
@[MyClass]
class Foo
pp {{ @type.annotation(MyClass).stringify }}
@[MyIvar]
@num : Int32 = 1
@[MyIvar]
property name : String = "jim"
def properties
{% for ivar in @type.instance_vars %}
pp {{ ivar.annotation(MyIvar).stringify }}
{% end %}
end
end
@[MyMethod]
def my_method
pp {{ @def.annotation(MyMethod).stringify }}
end
def method_params(
@[MyParameter(index: 0)]
value : Int32,
@[MyParameter(index: 1)] metadata,
@[MyParameter(index: 2)] & : -> String
)
pp {{ @def.args[0].annotation(MyParameter).stringify }}
pp {{ @def.args[1].annotation(MyParameter).stringify }}
pp {{ @def.block_arg.annotation(MyParameter).stringify }}
end
Foo.new.properties
my_method
method_params 10, false do
"foo"
end
pp {{ Foo.annotation(MyClass).stringify }}
# Which would print
"@[MyClass]"
"@[MyIvar]"
"@[MyIvar]"
"@[MyMethod]"
"@[MyParameter(index: 0)]"
"@[MyParameter(index: 1)]"
"@[MyParameter(index: 2)]"
"@[MyClass]"
警告
注释只能从类型化块参数中读取。查看 https://github.com/crystal-lang/crystal/issues/5334。
读取多个注释¶
#annotations
方法返回类型上所有注释的 ArrayLiteral
。可选地,带有 #annotations(type : TypeNode)
方法的 TypeNode
参数仅过滤提供的类型的注释。
annotation MyAnnotation; end
annotation OtherAnnotation; end
@[MyAnnotation("foo")]
@[MyAnnotation(123)]
@[OtherAnnotation(456)]
def annotation_read
{% for ann in @def.annotations(MyAnnotation) %}
pp "{{ann.name}}: {{ ann[0].id }}"
{% end %}
puts
{% for ann in @def.annotations %}
pp "{{ann.name}}: {{ ann[0].id }}"
{% end %}
end
annotation_read
# Which would print:
"MyAnnotation: foo"
"MyAnnotation: 123"
"MyAnnotation: foo"
"MyAnnotation: 123"
"OtherAnnotation: 456"