能否将任意方法组作为参数传递给一个方法?

26
我想编写一个类似以下的函数:
// The type 'MethodGroup' below doesn't exist.  This is fantasy-code.
public void MyFunction(MethodGroup g)
{
    // do something with the method group
}

后面的内容翻译如下:

后来,我可以使用MyFunction任何方法组一起调用。就像这样。

MyFunction(Object.Equals)

如果我承诺签名,那么事情就会顺利进行。
public void MyFunction(Func<object, object, bool> f)
{
    // do something with known delegate
}
...
MyFunction(Object.Equals)

方法组Object.Equals可以高兴地强制转换为已知委托类型Func<object,object,bool>,但我不想承诺特定的签名。我想将任何方法组传递给MyFunction

方法组无法转换为System.Object

public void MyFunction(object o)
{
    // do something with o
}
...
MyFunction(Object.Equals) // doesn't work

我认为每个人都曾在方法调用中忘记括号并在某些时候发现了这一点。我希望这不意味着方法组不能作为一等对象(或不能转换)。我认为 Linq 表达式无法给出我所需要的通用性,但我可能确实漏掉了什么。
我还应该提到,如果方法组包含重载,那也没关系,只要我有一种检查方法组的方法即可。
我可以做些什么来处理方法组?我可以打印组中所有方法的签名(重载、扩展方法等),或者我可以使用一些参数来“调用”该组(如果可能,将其解析为组中的正确重载)。这些事情有其他的做法,但这是你可能想要使用方法组的一些方式。
正如几个人已经提到的,我可以接受一个委托,并在调用 MyFunction 时将其强制转换为特定的已知委托类型。
public void MyFunction(Delegate d)
{
    // do something with d
}
...
MyFunction((Func<object, object, bool>)Object.Equals)

但这与传递整个方法组并不完全相同。这只选择了一种方法,并将其转换为特定的委托。我真的很想一次性传递整个组。

4个回答

10

我认为这里一个重要的问题是:如果你能够传递方法组,你会用它做什么?

请记住,方法组只是一组方法的名称 - 可能有一个或多个 - 它们可以是重载或扩展方法。方法组不是编译代码中保留的概念 - 编译器支持从方法组到委托的转换 - 但为了使这种转换成为可能,编译器必须能够明确解析出您打算传递的方法。

只有在函数明确定义了方法的签名时,方法组才是作为函数参数有效的。例如:public void MyFunction( Func<int> f ) 可以传递一个方法组。

最接近的方法是编写一个接受 Delegate 类型的函数:

public void MyFunction( Delegate d ) { ... }

但是你仍然无法传递方法组,因为不存在从方法组到委托的转换。你必须将其强制转换为特定的委托签名:

// call MyFunction with a particular delegate
MyFunction( (Func<string>)myObj.ToString );  // method group conversion may occur

17
回答你的问题,我会获取它的名称并记录下来。方法组中的每个方法都具有相同的名称和相同的目标对象。这有很多用途。 - skrebbel
1
如果您可以传递一个方法组,您会用它做什么?在我的情况下(网络编程),我想将方法作为参数传递,并获取其名称以便在接收端找到相应的方法。目前,我在任何地方都使用nameof并将其作为字符串传递,但如果我直接传递方法本身并且仅在方法内部获取名称,那将更加酷炫。 - derHugo
1
如果您想知道调用方法的名称,可以使用[CallerMemberName]来完全不同的方法进行操作。请参阅https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.callermembernameattribute?view=net-7.0。 - Jon Bangsberg

7
您可以传递一个委托:
public void MyFunction(Delegate d)
{
    // do something with the method group
}

然而,在调用方法时您将需要指定委托类型:

MyFunction(new Func<object, object, bool>(Object.Equals));

注意:上面的代码不是传递一个方法组,而是单个方法...我认为不可能传递一个方法组。为什么你要这样做呢?

3
询问者希望这样做是因为他想要关于方法组的元数据对于组内的任何方法都是相同的(例如名称),或者他想要对组内所有方法进行操作。我认为nameof (C#参考)在这里可能会最有用。
我将MyFunction定义为他最初期望的那样,但重要的是要注意,无论这个函数做什么,它只适用于一个方法签名。如果MethodInfo提供了您需要的有关函数的所有信息,则可能实际上不需要调用此函数。
public void MyFunction(Delegate d)
{
    // do something with d
}

接下来,我们将提供一个包装器,它需要传入方法名、类型和可选的调用对象。即使是实例方法,也要求传入调用对象来生成委托,但如果委托为空,则实际执行委托可能会失败。为静态方法提供调用对象会导致绑定错误,即使只是用于创建委托。我已编写了下面的方法来返回静态和实例方法,并将任何实例方法绑定到调用者,而静态方法则绑定到空值。您可以修改它以在调用者为实例时跳过静态方法,反之亦然。

public void MyFunctionMethodGroup(string methodName, Type type, object caller)
{
    var methods = type.GetMethods().Where(x => x.Name == methodName);
    foreach(var method in methods)
    {
        Delegate myDelegate = method.CreateDelegate(Expression.GetDelegateType(
            method.GetParameters().Select(x => x.ParameterType)
            .Concat(new[] { method.ReturnType })
            .ToArray()), method.IsStatic ? null : caller);
        MyFunction(myDelegate);
    }
}

以下是几个调用示例。 静态方法
MyFunctionMethodGroup(nameof(String.IsNullOrEmpty), typeof(String), null);

实例方法

请注意,即使是实例函数,也可以从类名调用该函数。如果您不需要调用该方法并且没有调用者,则这很好。您还可以从实例中调用它,而无需显式编写类名。

string xyz = "2";
MyFunctionMethodGroup(nameof(String.Contains), typeof(String), xyz);
MyFunctionMethodGroup(nameof(xyz.Contains), xyz.GetType(), xyz);

高级案例

扩展方法

值得记住的是,扩展方法被称为在定义它们的命名空间上的静态方法。如果你需要实例参数来完成MyFunction中想要做的事情,那么它必须作为一个新的参数传递进来,与调用者分开,因为扩展方法是静态的。

泛型方法

此外,上面的代码不能正确地处理泛型方法。以下是应用该方法的泛型类型的示例。

   public void MyFunctionMethodGroup(string methodName, Type type, object caller = null, params Type[] genericParameters)
    {
        //GetMethods only returns public methods. If you need protected/private, modify as appropriate.
        var methods = type.GetMethods().Where(x => x.Name == methodName);
        foreach (var method1 in methods)
        {
            MethodInfo method = method1.IsGenericMethod ? method1.MakeGenericMethod(genericParameters) : method1;
            Type delegateType = Expression.GetDelegateType(
                method.GetParameters().Select(x => x.ParameterType)
                .Concat(new[] { method.ReturnType })
                .ToArray());
            Delegate myDelegate = method.CreateDelegate(delegateType, method.IsStatic ? null : caller);
            MyFunction(myDelegate);
        }
    }

通用类

如果未在Type参数中定义泛型类型,则上述方法可能无法完全支持通用类中的方法。

输出/引用参数

所编写的代码不支持输出或引用参数。它也可能不支持ref参数。


1

我认为你可以使用这样的签名:

MyFunc<T>(EventHandler<T> newDelegate)
{
    object storedDelegate = newDelegate;
    // after that you can use storedDelegate later
}

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