原文
D中的用户定义属性(UDA)
主要用来编译时反省
.它允许你用编译时元数据
标记声明
,然后其他代码可在编译时
从中检查这些元数据
.
如命令行选项
解析代码
,传递"--help"
选项时,它会打印出自动
生成的帮助文本
.想法是在结构中封装
命令行选项,如:
struct Options
{
double xmin;
double xmax;
double ymin;
double ymax;
int xres;
int yres;
}
可用编译时反省
来迭代结构成员
并按选项名使用字段名
(如,这样你就可带'--xmin=1.0 --ymax=10.0'
参数等运行程序).
可在没有UDA
时就这样,但假设
想更进一步,让"--help"
自动打印
出所有可用
选项.当然,可手写
一个按大串
打印此信息的showHelp()
函数,但帮助文本
与Options
结构的定义
分离了.
如果随后修改"选项
"结构,则帮助文本
将过时,并且可能包含误导性或错误
信息.如果可直接嵌入帮助文本
到选项定义
中会更好,这样"--help"
将*总是*
是最新的.
此方法是给它添加UDA
:
struct Desc { string text; }
struct Options
{
@Desc("Minimum X-coordinate") double xmin;
@Desc("Maximum X-coordinate") double xmax;
@Desc("Minimum Y-coordinate") double ymin;
@Desc("Maximum Y-coordinate") double ymax;
@Desc("Output horizontal resolution") int xres;
@Desc("Output vertical resolution") int yres;
}
这些UDA
自身无操作.但是现在你可在showHelp()
函数中检查
它们,并按输出的一部分打印出UDA
串:
void showHelp() {
Options opts;
writefln("Usage: myprogram [options]");
writeln("Options:");
foreach (field; __traits(allMembers(Options))) {
alias desc = getUDAs!(mixin("opts."~field), Desc);
writefln("--%s", field);
if (desc.length > 0)
writefln("\t%s", desc);
}
}
现在showHelp()
打印出选项
中每个字段的描述
,并且只要在更改字段
定义时,更新
选项中的UDA
,它会总是显示
正确信息.
还可自动
执行选项区间
.如,如果Options.xres
必须介于10
和5000
之间,则可显式
编写代码来检查这一点,但同样,检查定义
与选项
分离了,因此它可能会不同步.
但是使用UDA
,可添加此信息
到选项定义
中,并让解析选项
的代码自动强制
:
struct Desc { string text; }
struct Range { int minVal, maxVal; }
struct Options
{
@Desc("Minimum X-coordinate") double xmin;
@Desc("Maximum X-coordinate") double xmax;
@Desc("Minimum Y-coordinate") double ymin;
@Desc("Maximum Y-coordinate") double ymax;
@Range(10, 5000) @Desc("Output horizontal resolution") int xres;
@Range(8, 4000) @Desc("Output vertical resolution") int yres;
}
然后在解析选项
的代码中:
void parseOption(ref Options opts, string name, string value) {
foreach (field; __traits(allMembers(Options))) {
if (name == field) {
import std.conv : to;
alias T = typeof(mixin("opts."~field));
T val = value.to!T; //解析选项值
//自动强制选项值区间
alias range = getUDAs!(mixin("opts."~field), Range);
if (range.length > 0) {
enforce(val >= range.minVal,
"value of "~field~" is below minimum");
enforce(val <= range.maxVal,
"value of "~field~" is above maximum");
}
//通过检查区间(或未找到`UDA`区间),把解析的值`赋值`给`选项结构`.
mixin("opts."~field) = val;
}
}
}
现在只需更新"选项
"中的UDA
区间,parseOption
会自动强制
新区间.在A
位置更改
代码,会自动更新检查UDA
的所有
其他代码.
还可用UDA
标记需要
特殊处理的选项
.如,假设选项中的int
字段,一般只是从10
基数转换而来,但一个指定
的字段(如"octalvalue"
)需要用户输入八进制
.
与其在parseOption
中为每个此类
异常编写if-then-else
意大利面条,不如创建一个附加到"octalvalue"
定义中的如Octal
的新的UDA
.
然后在parseOption()
中,检查此UDA
字段,如果存在
,则用8为基数
而不是用10
为基数来解析值.
对只用一个if
语句就可完成的事情来说,这似乎太多,但是如果
未来需要来另一个八进制
字段怎么办?使用UDA
,只需在选项
定义中附加
它,一切都会正常工作(tm)
.
浏览选项
定义,会立即告诉你按八进制
解析此字段
;无需深入研究parseOption()
代码就知道.UDA
本身就是文档,这是件好事.