Any declaration (e.g. struct type, class type, variable, etc.) can be assigned attributes, which can then be accessed at compile time to alter the way the code is compiled. User defined attributes is purely a compile-time feature.
任何声明(例如结构类型、类类型、变量等)都可以被赋值属性,然后可以在编译时访问这些属性,以改变代码编译的方式。用户定义的属性纯粹是编译时特性。
The user defined attribute syntax consists of the @ sign followed by the attribute and appear before the declaration that it is being assigned to. For example, the following code assigns the Encrypted attribute to the declaration of name:
@Encrypted string name;
Multiple attributes can be specified separately or as a parenthesized list of attributes. For example, both of the following variables have the same attributes:
@Encrypted @Colored string lastName; // ← separately
@(Encrypted, Colored) string address; // ← together
An attribute can be a type name as well as a value of a user defined or a fundamental type. However, because their meanings may not be clear, attributes consisting of literal values like 42 are discouraged:
属性可以是类型名,也可以是用户定义的值或基本类型。但是,因为它们的含义可能不清楚,所以不建议使用42这样的文字值组成的属性。
struct Encrypted {
}
enum Color { black, blue, red }
struct Colored {
Color color;
}
void main() {
@Encrypted int a; // ← type name
@Encrypted() int b; // ← object
@Colored(Color.blue) int c; // ← object
@(42) int d; // ← literal (discouraged)
}
The attributes of a and b above are of different kinds: The attribute of a is the type Encrypted itself, while the attribute of b is an object of type Encrypted. This is an important difference that affects the way attributes are used at compile time. We will see an example of this difference below.
The meaning of attributes is solely determined by the programmer for the needs of the program. The attributes are determined by __traits(getAttributes) at compile time and the code is compiled according to those attributes.
属性的含义完全由程序员根据程序的需要来决定。属性在编译时由
__traits(getAttributes)决定,代码根据这些属性编译。
The following code shows how the attributes of a specific struct member (e.g. Person.name) can be accessed by __traits(getAttributes):
import std.stdio;
// ...
struct Person {
@Encrypted @Colored(Color.blue) string name;
string lastName;
@Colored(Color.red) string address;
}
void main() {
foreach (attr; __traits(getAttributes, Person.name)) {
writeln(attr.stringof);
}
}
The output of the program lists the attributes of Person.name:
Encrypted
Colored(cast(Color)1)
Two other __traits expressions are useful when dealing with user defined attributes:
__traits(allMembers)produces the members of a type (or a module) as strings.__traits(getMember)produces a symbol useful when accessing a member. Its first argument is a symbol (e.g. a type or a variable name) and its second argument is a string. It produces a symbol by combining its first argument, a dot, and its second argument. For example,__traits(getMember, Person, "name")produces the symbolPerson.name.
__traits(allMembers)生成类型(或模块)的成员为字符串。__traits(getMember)在访问成员时产生一个有用的符号。它的第一个参数是一个符号(例如类型或变量名),第二个参数是一个字符串。它通过组合它的第一个参数,一个点,和它的第二个参数来产生一个符号。例如,__traits(getMember, Person, "name")产生符号Person.name。
import std.string;
// ...
void main() {
foreach (memberName; __traits(allMembers, Person)) {
writef("The attributes of %-8s:", memberName);
foreach (attr; __traits(getAttributes,
__traits(getMember,
Person, memberName))) {
writef(" %s", attr.stringof);
}
writeln();
}
}
The output of the program lists all attributes of all members of Person:
The attributes of name : Encrypted Colored(cast(Color)1)
The attributes of lastName:
The attributes of address : Colored(cast(Color)2)
Another useful tool is std.traits.hasUDA, which determines whether a symbol has a specific attribute. The following static assert passes because Person.name has Encrypted attribute:
另一个有用的工具是
std.traits.hasUDA,它决定一个符号是否具有特定的属性。
import std.traits;
// ...
static assert(hasUDA!(Person.name, Encrypted));
hasUDA can be used with an attribute type as well as a specific value of that type. The following static assert checks both pass because Person.name has Colored(Color.blue) attribute:
static assert(hasUDA!(Person.name, Colored));
static assert(hasUDA!(Person.name, Colored(Color.blue)));
89.1 Example
Let's design a function template that prints the values of all members of a struct object in XML format. The following function considers the Encrypted and Colored attributes of each member when producing the output:
void printAsXML(T)(T object) {
// ...
foreach (member; __traits(allMembers, T)) { // (1)
string value =
__traits(getMember, object, member).to!string; // (2)
static if (hasUDA!(__traits(getMember, T, member), // (3)
Encrypted)) {
value = value.encrypted.to!string;
}
writefln(` <%1$s color="%2$s">%3$s</%1$s>`, member,
colorAttributeOf!(T, member), value); // (4)
}
}
The highlighted parts of the code are explained below:
- The members of the type are determined by
__traits(allMembers). - The value of each member is converted to string to be used later when printing to the output. For example, when the member is "name", the right-hand side expression becomes
object.name.to!string. - Each member is tested with
hasUDAto determine whether it has theEncryptedattribute. The value of the member is encrypted if it has that attribute. (BecausehasUDArequires symbols to work with, note how__traits(getMember)is used to get the member as a symbol (e.g.Person.name).) - The color attribute of each member is determined with
colorAttributeOf(), which we will see below.
The colorAttributeOf() function template can be implemented as in the following code:
Color colorAttributeOf(T, string memberName)() {
foreach (attr; __traits(getAttributes, __traits(getMember, T, memberName))) {
static if (is (typeof(attr) == Colored)) {
return attr.color;
}
}
return Color.black;
}
When the compile-time evaluations are completed, the printAsXML() function template would be instantiated for the Person type as the equivalent of the following function:
/* The equivalent of the printAsXML!Person instance. */
void printAsXML_Person(Person object) {
// ...
{
string value = object.name.to!string;
value = value.encrypted.to!string;
writefln(` <%1$s color="%2$s">%3$s</%1$s>`,
"name", Color.blue, value);
}
{
string value = object.lastName.to!string;
writefln(` <%1$s color="%2$s">%3$s</%1$s>`,
"lastName", Color.black, value);
}
{
string value = object.address.to!string;
writefln(` <%1$s color="%2$s">%3$s</%1$s>`,
"address", Color.red, value);
}
}
The complete program has more explanations:
import std.stdio;
import std.string;
import std.algorithm;
import std.conv;
import std.traits;
/* Specifies that the symbol that it is assigned to should be
* encrypted. */
struct Encrypted {
}
enum Color { black, blue, red }
/* Specifies the color of the symbol that it is assigned to.
* The default color is Color.black. */
struct Colored {
Color color;
}
struct Person {
/* This member is specified to be encrypted and printed in
* blue. */
@Encrypted @Colored(Color.blue) string name;
/* This member does not have any user defined
* attributes. */
string lastName;
/* This member is specified to be printed in red. */
@Colored(Color.red) string address;
}
/* Returns the value of the Colored attribute if the specified
* member has that attribute, Color.black otherwise. */
Color colorAttributeOf(T, string memberName)() {
auto result = Color.black;
foreach (attr;
__traits(getAttributes,
__traits(getMember, T, memberName))) {
static if (is (typeof(attr) == Colored)) {
result = attr.color;
}
}
return result;
}
/* Returns the Caesar-encrypted version of the specified
* string. (Warning: Caesar cipher is a very weak encryption
* method.) */
auto encrypted(string value) {
return value.map!(a => dchar(a + 1));
}
unittest {
assert("abcdefghij".encrypted.equal("bcdefghijk"));
}
/* Prints the specified object in XML format according to the
* attributes of its members. */
void printAsXML(T)(T object) {
writefln("<%s>", T.stringof);
scope(exit) writefln("</%s>", T.stringof);
foreach (member; __traits(allMembers, T)) {
string value =
__traits(getMember, object, member).to!string;
static if (hasUDA!(__traits(getMember, T, member),
Encrypted)) {
value = value.encrypted.to!string;
}
writefln(` <%1$s color="%2$s">%3$s</%1$s>`,
member, colorAttributeOf!(T, member), value);
}
}
void main() {
auto people = [ Person("Alice", "Davignon", "Avignon"),
Person("Ben", "de Bordeaux", "Bordeaux") ];
foreach (person; people) {
printAsXML(person);
}
}
The output of the program shows that the members have the correct color and that the name member is encrypted:
<Person>
<name color="blue">Bmjdf</name> ← blue and encrypted
<lastName color="black">Davignon</lastName>
<address color="red">Avignon</address> ← red
</Person>
<Person>
<name color="blue">Cfo</name> ← blue and encrypted
<lastName color="black">de Bordeaux</lastName>
<address color="red">Bordeaux</address> ← red
</Person>
89.2 The benefit of user defined attributes
The benefit of user defined attributes is being able to change the attributes of declarations without needing to change any other part of the program. For example, all of the members of Person can become encrypted in the XML output by the trivial change below:
用户定义属性的好处是能够更改声明的属性,而不需要更改程序的任何其他部分。
struct Person {
@Encrypted {
string name;
string lastName;
string address;
}
}
// ...
printAsXML(Person("Cindy", "de Cannes", "Cannes"));
The output:
<Person>
<name color="black">Djoez</name> ← encrypted
<lastName color="black">ef!Dbooft</lastName> ← encrypted
<address color="black">Dbooft</address> ← encrypted
</Person>
Further, printAsXML() and the attributes that it considers can be used with other types as well:
struct Data {
@Colored(Color.blue) string message;
}
// ...
printAsXML(Data("hello world"));
The output:
<Data>
<message color="blue">hello world</message> ← blue
</Data>