ユーザー定義属性(UDA)
あらゆる宣言(構造体型、クラス型、変数など)に属性を割り当てることができ、その属性はコンパイル時にアクセスしてコードのコンパイル方法を変更することができる。ユーザー定義属性は、純粋にコンパイル時の機能だ。
ユーザー定義属性の構文は、@
記号とそれに続く属性で構成され、属性が割り当てられる宣言の前に記述する。例えば、次のコードは、Encrypted
属性をname
の宣言に割り当てている。
複数の属性は、個別に指定することも、括弧で囲んだ属性のリストとして指定することもできる。例えば、次の2つの変数は同じ属性を持っている。
属性は、型名だけでなく、ユーザー定義型または基本型の値にもすることができる。ただし、その意味が明確でない場合があるため、42
のようなリテラル値で構成される属性は使用しないことをお勧めする。
上記のa
とb
の属性は、種類が異なる。a
の属性は型Encrypted
自体だが、b
の属性は型 Encrypted
のオブジェクトだ。これは、コンパイル時に属性の使用方法に影響を与える重要な違いだ。この違いの例を以下に示す。
属性の意味は、プログラムの要件に応じてプログラマーが独自に決定する。属性は、コンパイル時に__traits(getAttributes)
によって決定され、その属性に従ってコードがコンパイルされる。
以下のコードは、特定のstruct
メンバー(例:Person.name
)の属性を__traits(getAttributes)
からアクセスする方法を示している:
プログラムの出力には、Person.name
の属性が一覧表示される:
Encrypted
Colored(cast(Color)1)
ユーザー定義の属性を扱う場合、次の2つの__traits
式も便利だ。
-
__traits(allMembers)
は、型(またはモジュール)のメンバーを文字列として生成する。 -
__traits(getMember)
は、メンバーにアクセスするときに便利なシンボルを生成する。最初の引数はシンボル(型名や変数名など)で、2番目の引数は文字列だ。最初の引数、ドット、2番目の引数を組み合わせてシンボルを生成する。例えば、__traits(getMember, Person,
"name"
)Person.name
というシンボルを生成する。
プログラムの出力は、Person
のすべてのメンバーのすべての属性を一覧表示する:
nameの属性 | Encrypted Colored(cast(Color)1) |
---|---|
lastNameの属性 | |
addressの属性 | Colored(cast(Color)2) |
もう1つの便利なツールはstd.traits.hasUDA
で、シンボルが特定の属性を持つかどうかを判定する。次のstatic assert
は、Person.name
がEncrypted
属性を持つため、パスする:
hasUDA
は、属性型だけでなく、その型の特定の値とも使用できる。次のstatic assert
は、Person.name
にColored(Color.blue)
属性があるため、両方ともチェックに合格する。
例
struct
オブジェクトのすべてのメンバーの値をXML形式で出力する関数テンプレートを設計しよう。次の関数は、出力時に各メンバーのEncrypted
およびColored
属性を考慮する。
コードのハイライト部分は以下で説明する:
- 型のメンバーは、
__traits(allMembers)
によって決定される。 - 各メンバーの値は、後で出力に表示する際に使用するために、
string
に変換される。例えば、メンバーが"name"
の場合、右側の式はobject.name.to!string
になる。 - 各メンバーは、
hasUDA
でテストされ、Encrypted
属性があるかどうかが判断される。その属性がある場合、メンバーの値は暗号化される。(hasUDA
はシンボルで動作するため、__traits(getMember)
を使用してメンバーをシンボルとして取得する方法(例:Person.name
)に注意。) - 各メンバーの色属性は、
colorAttributeOf()
で決定される。これは後で説明する。
colorAttributeOf()
関数テンプレートは、次のコードのように実装できる。
コンパイル時の評価が完了すると、printAsXML()
関数テンプレートは、Person
型に対して、以下の関数と同等のインスタンス化される。
完全なプログラムにはさらに説明がある:
プログラムの出力は、メンバーが正しい色を持ち、name
メンバーが暗号化されていることを示している:
<Person>
<name color="blue">Bmjdf</name> ← 青色で暗号化済み
<lastName color="black">Davignon</lastName>
<address color="red">Avignon</address> ← 赤
</Person>
<Person>
<name color="blue">Cfo</name> ← 青色で暗号化済み
<lastName color="black">de Bordeaux</lastName>
<address color="red">Bordeaux</address> ← 赤
</Person>
ユーザー定義属性の利点
ユーザー定義属性の利点は、プログラムの他の部分を変更することなく、宣言の属性を変更できることだ。例えば、以下の簡単な変更により、Person
のすべてのメンバーをXML出力で暗号化することができる。
出力:
<Person>
<name color="black">Djoez</name> ← 暗号化済み
<lastName color="black">ef!Dbooft</lastName> ← 暗号化済み
<address color="black">Dbooft</address> ← 暗号化済み
</Person>
さらに、printAsXML()
およびそれが考慮する属性は、他の型でも使用することができる。
出力:
<Data>
<message color="blue">hello world</message> ← 青
</Data>
要約
- ユーザー定義属性は、任意の宣言に割り当てることができる。
- ユーザー定義の属性は、型名だけでなく値にも指定できる。
- ユーザー定義属性は、
hasUDA
および__traits(getAttributes)
によってコンパイル時にアクセスされ、プログラムのコンパイル方法を変更することができる。