目前是否可以跨模块扫描/查询/迭代所有具有某些属性的函数(或类)?
例如:
source/packageA/something.d:
@sillyWalk(10)
void doSomething()
{
}
source/packageB/anotherThing.d:
@sillyWalk(50)
void anotherThing()
{
}
source/main.d:
void main()
{
for (func; /* All @sillWalk ... */) {
...
}
}
答案 0 :(得分: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
您希望快速查看c.d
,b.d
和main.d
以查看用法。 onEach
进程中的main.d
函数每次点击帮助函数找到,这里只是打印名称。在main
函数中,您会看到一个疯狂的mixin(__MODULE__)
- 这是一个hacky技巧,可以将当前模块的引用作为迭代的起点。
另请注意,main.d
文件顶部有module project.main;
行 - 如果模块名称只是main
,因为它是自动的,没有声明,mixin
黑客会使模块混淆函数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;
}
}
}
}
首先,我们有属性定义和一些帮助来检测它的存在。如果您以前使用过UDA,那么这里没有什么新东西 - 只需扫描我们感兴趣的类型的属性元组。
helper
模板是缩写重复调用__traits(getMember)
的一种技巧 - 它只是将其别名化为更好的名称,同时避免编译器中出现愚蠢的解析错误。
最后,我们有步行者的肉。它循环遍历allMembers
,D编译时反思的主力(如果你不熟悉这一点,请看看我的D Cookbook的样本章节https://www.packtpub.com/application-development/d-cookbook - " Free Sample"链接是关于编译时反射的章节)
接下来,第一个static if
只是确保我们能够真正获得我们想要的成员。如果没有这个,它会在尝试获取自动导入的object
模块的私有成员时抛出错误。
函数的结尾也很简单 - 它只是在每个元素上调用我们的onEach
事物。但中间是神奇的地方:如果它检测到一个模块(sooo hacky btw但我只知道这样做)在walk中导入,它在这里导入它,通过使用的mixin(module)
技巧获得访问权限在顶层...因此通过程序的导入图表进行递归。
如果你玩游戏,你会发现它确实有点有效。 (在命令行btw上一起编译所有这些文件以获得最佳结果:dmd main.d attr.d b.d c.d
)
但它也有许多限制:
可以进入class / struct成员,但这里没有实现。但是很简单:如果成员是一个类,也可以递归地进入它。
如果模块与成员共享名称,则可能会中断,例如上面提到的main
示例。通过使用带有一些包点的唯一模块名称来解决这个问题应该没问题。
它不会进入函数本地导入,这意味着可以在程序中使用一个不会被这个技巧拾取的函数。我今天不知道D中有任何解决方案,即使你愿意使用该语言中的每一个黑客也是如此。
使用UDA添加代码总是很棘手,但这里要加倍,因为onEach
是一个具有on范围的函数。您可以建立一个全局关联的委托数组到处理程序中,但是:void delegate()[string] handlers; /* ... */ handlers[memberName] = &localHandlerForThis;
类似于运行时访问信息的东西。
我认为它也无法在更复杂的东西上编译,我现在只是把它作为玩具的概念验证。
大多数D代码,而不是像这样尝试遍历导入树,只需要在单个聚合或模块中使用mixin UdaHandler!T;
,例如:每个人之后mixin RegisterSerializableClass!MyClass;
。也许不是超级DRY,但更可靠。
编辑: 还有一个在我最初写答案时没有注意到的错误:"模块b.d;"实际上并没有被接受。将其重命名为"模块b;"有效,但不包括包裹。
ooooh cuz它被认为是"包mod"在stringof ....没有成员。也许如果编译器只是调用它"模块foo.bar"而不是"包foo"我们虽然在做生意。 (当然这对于应用程序编写者来说并不实用......这有时会破坏这个技巧的实用性)