目前是否可以跨模块扫描/查询/迭代所有具有某些属性的函数(或类)?
例如:
source/packageA/something.d:
@sillyWalk(10)
void doSomething()
{
}
Run Code Online (Sandbox Code Playgroud)
source/packageB/anotherThing.d:
@sillyWalk(50)
void anotherThing()
{
}
Run Code Online (Sandbox Code Playgroud)
source/main.d:
void main()
{
for (func; /* All @sillWalk ... */) {
...
}
}
Run Code Online (Sandbox Code Playgroud)
Ada*_*ppe 37
信不信由你,但是,它有点......虽然它真的很hacky并且有很多漏洞.代码:http://arsdnet.net/d-walk/
运行将打印:
Processing: module main
Processing: module object
Processing: module c
Processing: module attr
test2() has sillyWalk
main() has sillyWalk
Run Code Online (Sandbox Code Playgroud)
你会想采取快速浏览一下c.d
,b.d
和main.d
看到的使用.该onEach
函数main.d
处理每一击辅助功能发现,这里只是打印的名称.在main
函数中,你会看到一个疯狂的外观mixin(__MODULE__)
- 这是一个hacky技巧,以获取对当前模块的引用作为我们的迭代的起点.
还要注意main.d
文件module project.main;
顶部排成一行 - 如果模块名称就像main
没有声明那样自动生成,那么mixin
hack会使模块混淆功能main
.这段代码真的很脆弱!
现在,请注意attr.d
:http://arsdnet.net/d-walk/attr.d
module attr;
struct sillyWalk { int i; }
enum isSillyWalk(alias T) = is(typeof(T) == sillyWalk);
import std.typetuple;
alias hasSillyWalk(alias what) = anySatisfy!(isSillyWalk, __traits(getAttributes, what));
enum hasSillyWalk(what) = false;
alias helper(alias T) = T;
alias helper(T) = T;
void allWithSillyWalk(alias a, alias onEach)() {
pragma(msg, "Processing: " ~ a.stringof);
foreach(memberName; __traits(allMembers, a)) {
// guards against errors from trying to access private stuff etc.
static if(__traits(compiles, __traits(getMember, a, memberName))) {
alias member = helper!(__traits(getMember, a, memberName));
// pragma(msg, "looking at " ~ memberName);
import std.string;
static if(!is(typeof(member)) && member.stringof.startsWith("module ")) {
enum mn = member.stringof["module ".length .. $];
mixin("import " ~ mn ~ ";");
allWithSillyWalk!(mixin(mn), onEach);
}
static if(hasSillyWalk!(member)) {
onEach!member;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
首先,我们有属性定义和一些帮助来检测它的存在.如果您之前使用过UDA,那么这里没有什么新东西 - 只需扫描我们感兴趣的类型的属性元组.
该helper
模板来简化重复调用了一招__traits(getMember)
-它只是它的别名一个更好的名字,同时避免在编译器中一个愚蠢的语法错误.
最后,我们有步行者的肉.它循环allMembers
,D编译时反射的主力(如果你不熟悉这个,请看看我的D Cookbook https://www.packtpub.com/application-development/d-cookbook的示例章节- "免费样本"链接是关于编译时反射的章节"
接下来,第一个static if
确保我们可以实际获得我们想要的成员.没有它,它会在尝试获取自动导入object
模块的私有成员时抛出错误.
函数的结尾也很简单 - 它只是onEach
在每个元素上调用我们的东西.但中间是神奇的地方:如果它检测到一个模块(sooo hacky btw但我只知道这样做)在walk中导入,它在这里导入它,通过mixin(module)
顶层使用的技巧获得访问权限. ..因此通过程序的导入图递归.
如果你玩,你会发现它实际上有点工作.(编译所有这些文件一起BTW以获得最佳效果的命令行:dmd main.d attr.d b.d c.d
)
但它也有许多局限性:
进入类/结构成员是可能的,但这里没有实现.虽然很简单:如果成员是一个类,那么也可以递归地进入它.
如果模块与成员共享名称,则可能会中断,例如main
上面提到的示例.通过使用带有一些包点的唯一模块名称来解决这个问题应该没问题.
它不会下降到函数本地导入,这意味着可以在程序中使用一个不会被这个技巧拾取的函数.今天我不知道在D中有任何解决方案,即使你愿意使用该语言中的每一个黑客.
使用UDA添加代码总是很棘手,但这里要加倍,因为它onEach
是一个带有on scope的函数.你可以建立一个全局关联的委托数组到处理程序中的东西:void delegate()[string] handlers; /* ... */ handlers[memberName] = &localHandlerForThis;
运行时访问信息的东西.
我打赌它也无法在更复杂的东西上编译,我现在只是把它作为玩具概念验证.
大多数D代码,而不是像这样尝试遍历导入树,只需要mixin UdaHandler!T;
在单独的聚合或模块中使用它,例如mixin RegisterSerializableClass!MyClass;
在每个代码之后.也许不是超级干,但更可靠.
编辑:在最初编写答案时我还没注意到另一个错误:"module bd;" 实际上并没有被接受.将其重命名为"模块b;" 有效,但不包括包裹.
ooooh因为它在stringof ....中被认为是"package mod",它没有成员.也许如果编译器只是称它为"module foo.bar"而不是"package foo",我们就会开展业务.(当然这对于应用程序编写者来说是不切实际的......这有时会破坏这个技巧的用处)