註解¶
註解可以用來將元數據添加到原始碼中的某些功能。類型、方法、實例變數和方法/巨集參數可以被註解。使用者定義的註解,例如標準函式庫的 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
就是這方面的例子。
ORMs¶
可以使用註解來將屬性指定為 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"