这里的D学习者...如果我有一个字符串(仅在运行时已知的值),这是我想要调用的函数的名称,我该怎么做?以下示例......
mapping: x = data2[, i]
答案 0 :(得分:11)
这是使用编译时反射的示例。使用__traits(allMembers)
,我们可以循环遍历聚合(模块,结构,类等)中的所有成员的名称,并使用__traits(getMember)
,我们可以按名称获取成员并执行调用它的操作
棘手的部分是getMember
需要编译时字符串,所以我们不能直接将命令行参数传递给它。相反,我们构建一个switch
来从参数派遣 - 几乎就像你手工一样,但不是自己编写所有名称,而是让循环处理它。
这里只有两个功能,但它可以扩展到任意数量,而无需修改main
功能。
在线查看更多评论:
import std.stdio;
// I'm grouping all the commands in a struct
// so it is easier to loop over them without
// other stuff getting in the way
struct Commands {
// making them all static so we don't need to instantiate it
// to call commands. This is often not the best way but it makes
// for an easy demo and does work well a lot of the time.
static:
// Also assuming they all return void and have no arguments.
// It is possible to handle other things, but it gets a lot
// more involved. (I think my book example goes partially into
// it, or something like my web.d goes all the way and generates
// web/http and javascript/json apis from a full signature but that
// code is pretty unreadable...)
void func001() {
writef("func001 called\n");
}
void func002() {
writef("func002 called\n");
}
}
void main(string[] args) {
if(args.length > 1)
// we switch on the runtime value..
// the label will be used below
outer: switch(args[1]) {
// then loop through the compile time options to build
// the cases. foreach with a compile time argument works
// a bit differently than runtime - it is possible to build
// switch cases with it.
//
// See also: http://dlang.org/traits.html#allMembers
// and the sample chapter of my book
foreach(memberName; __traits(allMembers, Commands)) {
case memberName:
// get the member by name with reflection,
// and call it with the parenthesis at the end
__traits(getMember, Commands, memberName)();
// breaking from the labeled switch so we don't fallthrough
// and also won't break the inner loop, which we don't want.
break outer;
}
default: // default is required on most D switches
writef("No such function, %s!\n", args[1]);
break;
}
else { // insufficient args given
writeln("Argument required. Options are:");
// we can also loop to list names at runtime
foreach(memberName; __traits(allMembers, Commands)) {
writeln(memberName);
}
}
}
答案 1 :(得分:5)
您也可以使用关联数组,假设每个函数都匹配相同的原型:
module test;
import std.format, std.stdio, std.conv;
void func001() {
writeln(__FUNCTION__);
}
void func002() {
writeln(__FUNCTION__);
}
alias Proto = void function();
Proto[string] funcs;
// assign the functions to a string in the static constructor
static this() {
funcs["func001"] = &func001;
funcs["func002"] = &func002;
}
void main(string[] args) {
if (args.length < 2) return;
//!\ note that first argument is always the application exename /!\\
auto funcnum = to!uint(args[1]);
auto funcname = format("func%03d", funcnum);
// try to get the matching function pointer
Proto* f = funcname in funcs;
// call it if the function pointer is assigned
if (f != null) (*f)();
}
请注意,在您的初始示例中,您使用参数发生了错误。 args[0]
始终设置为应用程序exename。第一个自定义参数实际上是args[1]
。
如果将1或2作为参数传递并打印出来,我建议的解决方案将起作用:
test.func001
test.func002
或没有
答案 2 :(得分:3)
而不是将你的字符串(参数,运行时)转换为函数调用(主要是编译)并进入强烈的内存/运行时/ DLL的东西,你可以只做一个简单的if
语句
对你来说有些假,如果你想要我,我会愉快地把它翻译成D -
Given functions func001, func002, func003:
Read and store a string input
if the input is equal to "func001":
Call func001
else if input is equal to "func002":
Call func002
else if the input is equal to "func003":
Call func 003
else
Print "Not a valid function name. Available functions are func001, func002, and func003."
答案 3 :(得分:0)
您采取的方法很可能不是您最终想要做的。我强烈建议您阅读Command pattern,因为这很可能是您要做的事情。
PS。维基百科的文章给出了一些复杂的例子。简而言之,您的Command对象可以具有名称,您可以轻松拥有Command对象的映射,您可以按名称进行查找。真正的力量是你没有巨大的开关或类似的东西。相反,您只需传递一个您想要执行的命令,它就知道该做什么。
如果OOP方法对你不利,那么你就可以提出声明性解决方案。
我认为,可以定义一个名为command
的用户定义属性(UDA),并用它来注释你想要被视为“命令”的每个函数,然后就可以了使用编译时内省,就像Adam在他的示例中所做的那样设置所有内容,以便您可以在需要时执行这些功能。你可以有一张地图CommandDelegate[string]
,其中CommandDelegate只是void delegate()
或类似的......