将方法作为参数传递

5

我正在使用C#中的一个库,其中一个方法要求我将目标方法的字符串名称作为参数传递。

出于明显的原因,我想避免使用硬编码的字符串,因此我将编写一个中间工具方法,该方法获取方法的名称(可能是通过反射)并将其馈入库方法中。

我期望这个中间方法看起来像这样:

public void CallOtherMethod(???? inputMethod)
{
    string methodName = inputMethod.Name; // This gives me the method without the namespace, right?
    this.CallFinalMethod(methodName);
}

应该这样称呼:

this.CallOtherMethod(this.SomeOtherMethod);

然而,我在确定执行此操作需要的类型时遇到了一些问题。

我应该如何正确定义我的方法?

值得一提的是,我很乐意将其编写为库的扩展方法,但这与库的行为方式不完全兼容。


你真的需要名字吗?还是直接调用方法就可以了?你想要实现什么? - Panagiotis Kanavos
这个库是否有特定的签名要求?例如,所有方法都必须不带参数并返回void吗?还是该库能够调用您提供的任何方法? - dcastro
1
这个回答解决了你的问题吗?使用C#将方法作为参数传递 - Davide Cannizzo
4个回答

3
尝试像这样使用ActionFunc:
public void CallOtherMethod(Action method)
{
    string methodName = method.Method.Name;
    method.Invoke();
}

 public void AnotherMethod(string foo, string bar)
{
    // Do Something
}

电话:

CallOtherMethod( () => AnotherMethod("foo", "bar") );

methodName 将被设置为编译器生成的名称(即 <EnclosingMethod>b__0),而不是 AnotherMethod - dcastro
1
非常感谢你,卢卡斯!我完全忘记了方法有返回值,所以我需要使用 Func 而不是 Action。我从来没有想到过可以这样使用委托。 - Vesuvian
1
不支持Windows。这个答案似乎依赖于一个实现怪癖,可能会在没有通知的情况下改变。 - Panagiotis Kanavos
1
@Vesuvian,你真的想写只能在特定编译器上运行的代码吗?你应该要么:a)仅传递方法组,或b)使用表达式来评估lambda。不使用表达式从lambda中检索方法名称是有风险的。 - dcastro
1
@Vesuvian,你是使用lambda表达式,就像这个答案一样,还是直接传递命名方法?后者将导致该方法的名称在此处提供,前者不应该。如果这样做了,那么它将是不正确的 - Servy
显示剩余2条评论

3

如果您的库要求方法具有特定的签名(例如,0个参数,返回void),那么可以使用具有适当签名的委托。

例如,Action表示具有0个参数且返回void的方法。

public void CallOtherMethodMethodGroup(Action action)
{
    MethodInfo method = action.Method;

    //check that 'action' is not a compiler generated lambda
    if (!method.IsDefined(typeof (CompilerGeneratedAttribute)))
    {
        Console.WriteLine(method.Name);
    }
    else
        throw new InvalidOperationException("Use 'CallOtherMethodExpr' instead.");
}

使用方法组来调用它:

CallOtherMethodMethodGroup(AnotherMethod);

如果您的库接受任何签名的方法,那么您将需要传递lambda而不是方法组:
CallOtherMethodExpr(() => AnotherMethod("dummy", "arg"));

然而,当使用lambda表达式时,你需要将方法更改为接受Expression<Action>而不是Action。表达式可以被解释,委托则不能。

public void CallOtherMethodExpr(Expression<Action> expr)
{
    string methodName;

    if (expr.Body.NodeType == ExpressionType.Call)
    {
        var call = expr.Body as MethodCallExpression;
        methodName = call.Method.Name;
    }
    else
        throw new InvalidOperationException("Expression must contain a method call");

    Console.WriteLine(methodName);
}

0

我的理解是,问题不在于如何通过委托调用方法,而在于如何获取方法的名称而不使用魔法字符串。

使用反射并不能消除魔法字符串。您可以使用特定签名获取类型的所有方法,但仍然需要知道您要定位的方法的名称。简单的重命名重构仍然会破坏您的代码。

在这种情况下的典型解决方案是传递指向所需成员的表达式。这在MVVM框架中被广泛用于传递任何修改参数的名称。

以下方法将检查提供的表达式是否为实际方法调用并提取方法的名称:

    public static bool TryGetName(Expression<Action> expr, out string memberName)
    {
        memberName = null;
        var body = expr.Body as MethodCallExpression;
        if (body == null)
            return false;
        memberName = body.Method.Name;
        return true;
    }

这个方法可以这样调用:

bool success=TryGetName(()=>someObject.SomeMethod(), out name);

所有的Action和Func对象都继承自Action,这意味着SomeMethod实际上可能是一个函数。如果该方法包含参数,则调用将需要一些虚拟值:
bool success=TryGetName(()=>someObject.SomeMethod(null,"dummy",0), out name);

-2
你需要 Delegate
public void OtherMethod(Action method)
{
   MessageBox.Show("I am raised first.");
   string methodname = method.Method.Name;
   method.Invoke();
}

public void SomeMethod()
{
    some code ...
}

如果你使用一个命名方法来调用另一个方法

OtherMethod(SomeMethod);

methodName被设置为"SomeMethod",
但如果你使用匿名方法来调用它

OtherMethod(() => MessageBox.Show("The main thing ..."));

那么你将得到一个令人讨厌的编译器生成的名称,例如<YourNamespaceName_Load>b__0


@dcastro 获取方法名很容易,看看更新后的答案即可。 - Mahdi Tahsildari
1
methodName 将被设置为编译器生成的名称(即 <EnclosingMethod>b__0),而不是 AnotherMethod - dcastro
@dcastro 如果我使用匿名方法?是的,如果我使用命名方法?不是。 - Mahdi Tahsildari
这与你的代码显示的不一样。你的 methodName 永远不会被设置为 Show - dcastro
@dcastro,就像我之前说的那样,这是因为我使用了匿名方法。如果我这样调用它 OtherMethod(SomeNamedMethod); 那么 methodName 将被设置为 'SomeNamedMethod'。 - Mahdi Tahsildari
1
你不觉得这个信息足够相关,应该包含在答案中吗? - dcastro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接