【问题标题】:D: call a function using a string variable with its nameD:使用带有名称的字符串变量调用函数
【发布时间】:2015-08-13 16:29:50
【问题描述】:

D learner here...如果我有一个字符串(值仅在运行时知道),它是我要调用的函数的名称,我该怎么做?下面的例子...

void func001() {
//stuff
}

void func002() {
//stuff
}

// .........

void func100() {
//stuff
}

void main(char[][] args) {
  auto funcnum = to!uint(args[0]);
  auto funcname = format('func%03d', funcnum);
  ///// need to run the function named 'funcname' here
}

【问题讨论】:

标签: d


【解决方案1】:

这是一个使用编译时反射的示例。使用__traits(allMembers),我们可以遍历聚合中所有成员的名称(模块、结构、类等),使用__traits(getMember),我们可以按名称获取成员并执行诸如调用之类的操作。

棘手的部分是getMember 需要一个编译时字符串,所以我们不能直接将命令行参数传递给它。相反,我们构建了一个 switch 来从参数分派 - 几乎就像您手动操作一样,但您无需自己编写所有名称,而是让循环处理它。

这里只有两个函数,但它可以扩展到任意数量,而无需修改 main 函数。

查看更多内联 cmets:

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。我认为它会比实际情况复杂得多。
  • 开关也可以是最终的,然后为无效函数抛出异常,这似乎更适合实际应用程序的使用。当然取决于它的使用方式
【解决方案2】:

你也可以使用关联数组,假设每个函数都匹配同一个原型:

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

或者什么都没有

【讨论】:

  • 还要注意to!uint()不是nothrow,所以转换可以在try..catch语句中完成。如果失败,您可以分配一个默认值,以便保证 funcname 不是数组键。
【解决方案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."

【讨论】:

  • 在 D 中,您还可以使用编译时反射自动生成该链。我的书的免费示例章节详细介绍了如何执行此操作的示例:packtpub.com/application-development/d-cookbook 但是的,这是获得简短列表的最简单方法。
  • @AdamD.Ruppe 我明白了。你说出来很好,因为我不是 D 专业人士,但这个想法本身似乎比它应该的复杂得多。好听的名字。
  • 是的,我可以使用 switch 或 if/else 树,但我试图绕过硬编码,特别是因为将有 很多 函数可供选择from 并且列表可能会很长。我对编译时反射一无所知,但我会看看@AdamD.Ruppe 的书。
  • 我也会在另一个答案中给你写一个简短的例子。
  • @J.R.Allen 我们讨论了多久?因为通常 if else 树会更具可读性和简单性 - 也可能更合乎逻辑(这取决于您的程序的角色,但这只是我个人的无统计意见)。尽管第二个亚当似乎是一位专家,但我很确定他的答案不会更容易理解。祝你好运(:
【解决方案4】:

您所采用的方法很可能不是您最终想要做的。我强烈建议您阅读Command pattern,因为这很可能是您想要做的。

PS。维基百科的文章给出了一些复杂的例子。简而言之,您的 Command 对象可以有名称,并且您可以轻松地拥有一个 Command 对象的映射,您可以通过名称进行查找。真正的力量是你没有一个巨大的开关或类似的东西。相反,您只需传递一个您想要执行的命令,它就知道要做什么。

如果 OOP 方法不适合您,那么您可以通过练习提出声明式解决方案。

我相信,可以定义一个名为 command 的用户定义属性 (UDA),并使用它来注释您希望被视为“命令”的每个函数,然后您可以使用 compile - 时间内省,就像亚当在他的示例中所做的那样来设置所有内容,以便您可以在需要时执行这些功能。您可以拥有一张地图 CommandDelegate[string],其中 CommandDelegate 只是 void delegate() 或类似的...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-09-16
    • 2014-04-11
    • 1970-01-01
    • 2010-12-19
    • 2015-11-05
    • 2012-05-17
    • 2013-04-12
    相关资源
    最近更新 更多