如何在C#中将函数作为参数传递?

55
在C#中,是否可以将函数作为参数传递?我可以使用Func或Action类来做到这一点,但这迫使我立即声明整个函数签名。当我尝试使用Delegate时,会出现编译错误,说明它无法将方法组转换为委托。
我正在Axial上工作,尝试允许用户调用Web服务。我想要的是能够创建Visual Studio代理类,然后传入生成的函数。函数签名并不重要,因为生成的代码只使用函数名称。然而,我想传递函数而不是名称有两个原因:能够使用代理的Url属性以及如果Web服务不存在或在Visual Studio中更新,则会出现编译器错误。

public void AlertIt(object o) {
    Axial.DOM.Window.Alert(o.ToString());
}
public void CallAddService() {
    object[] param = new object[] { int.Parse(txtA.Text), int.Parse(txtB.Text) };
    Axial.ServerScript.CallWebService(new WSProxy.WS().Add, param, AlertIt, AlertIt);
}

class Axial.ServerScript {
    public void CallWebService(Delegate method, object[] param, Action<object> successCallback, Action<object> failureCallback) {
        // translate to javascript (already working)
    }
}
8个回答

55

我认为你想要的是:

static object InvokeMethod(Delegate method, params object[] args){
    return method.DynamicInvoke(args);
}

static int Add(int a, int b){
    return a + b;
}

static void Test(){
    Console.WriteLine(InvokeMethod(new Func<int, int, int>(Add), 5, 4));
}

打印出 "9"。


7
有没有不需要代码中的"new Func<int, int, int>"部分的方法?使用模式非常重要。 - Dan Goldstein
2
我能想到的唯一方法是让InvokeMethod采用Func<int, int, int>而不是Delegate,但这样你就需要为每个可能出现的Func<>类型参数组合提供不同的InvokeMethod重载。 - P Daddy
3
请注意,使用DynamicInvoke会带来高昂的成本 - 在可能的情况下,请使用类型化的委托和Invoke(速度更快)。 - Marc Gravell
谢谢这个。它真的帮助我向另一个开发人员解释这个。 - Ryan Ternier

45

将方法组、匿名方法或 Lambda 表达式转换为委托需要编译器知道确切的委托类型。不过,您可以使用 Lambda 表达式和捕获的变量来简化此过程:

public void InvokeMethod(Action action)
{
    action();
}

public int Add(int a, int b)
{
    return a + b;
}

public void Test()
{    
    InvokeMethod(() => Add(2, 3));
}

这基本上是以正常方式延迟调用,但通过将对 Add 的实际调用包装在普通的 Action 委托中来实现。

如果这不能满足您的要求,也许您可以告诉我们更多关于您真正想要实现的内容。

编辑:如果这是生成的代码,您可以将其强制转换为具有正确类型参数的 Func<...> - 假设没有太多的类型参数。除此之外,没有真正的方法可以仅传递方法组。偶尔会有人呼吁使用 "infoof(...)" 操作符(类似于 typeof 但适用于成员)来提供 MemberInfo,但实际上不存在这种操作符。


非常接近,但使API难以使用。我已经更新了问题,以明确我正在做什么。 - Dan Goldstein
顺便提一下,这段代码需要使用C# 3.0工具进行编译。(只需将项目设置为 .Net Framework 3.5) - Jon Adams
但是这里的Test()和InvokeMethod()都返回void,所以Add()的结果无法返回。 - Abhishikt N. Jain
@AbhishiktN.Jain:没有什么是在尝试返回Add的值。我们只是使用委托调用InvokeMethod,这个委托是将lambda表达式转换后的结果。你试过这段代码吗? - Jon Skeet

4
您需要先有一个委托对象
delegate int Operation(int a, int b)

然后它变成了:
public void InvokeMethod(Operation method, object target, object param)
{
    method((int) target, (int) param);
}

不需要调用Invoke。

和dbone一样,我不确定你为什么需要params[]数组。你能解释一下params的扩展用法吗?

另外,我需要更正你的问题,因为它会导致编译错误 :p


3
请看以下使用委托的示例:

委托示例

为什么要使用反射?参数数量会改变吗?还是您知道方法签名将保持不变(还记得C#支持params[]关键字吗)?

C#中的params

希望对您有所帮助。
Bones

3

那个系列不一定相关,但我读它是因为它很有趣。它给了我一个模板函数的想法,如果C#类型推断更好的话,它会起作用。 - Dan Goldstein

3

这是一个非常简单的例子,对于那些已经熟悉(C/C++/VB.NET/Python)风格的通过指针/引用传递函数(使用C#委托)的程序员来说:

        delegate void CALLBACK(String s);
        static void Main(string[] args)
        {

            Get("some string", testfunc);

            Util.pause();
        }

        static void Get(String s, CALLBACK x)
        {
            x(s);
        }


        static void testfunc(String s)
        {
            Console.WriteLine(s);
        }

2

如果您需要将方法作为参数传递,并且还需要捕获返回值以进行进一步处理,则上述示例将正常工作。 但是,如果您需要传递一个void返回类型的方法,则需要创建InvokeMethod函数的另一个版本。请参考下面的示例。

private static T retry<T>(Delegate method, params object[] args)
{
    for (int i = 0; i <= 3; i++)
    {
        try
        {
            return (T)method.DynamicInvoke(args);
        }
        catch (Exception ex)
        {
            if (i == 3)
            {
                logMessage(ex.Message);
            }
            Console.WriteLine("Retry count " + i);
            Thread.Sleep(10);
        }
    }
    return default(T);
}

private static void retry2(Delegate method, params object[] args)
{
    for (int i = 0; i <= 3; i++)
    {
        try
        {
            method.DynamicInvoke(args);
            break;
        }
        catch (Exception ex)
        {
            if (i == 3)
            {
                logMessage(ex.Message);
                //return default(T);
            }
            Console.WriteLine("Retry count " + i);
            Thread.Sleep(10);
        }
    }
}
static bool isSuccess = true;
static void logMessage(string msg)
{
    isSuccess = false;
    Console.WriteLine(msg);
}

static int Add(int a, int b)
{
    return a + b;
}

static void Add2(int a, int b)
{
    int c = a + b;
    Console.WriteLine(c);
}

static void Main(string[] args)
{
    int d = retry<int>(new Func<int, int, int>(Add), 6, 7.7);
    Console.Write("  " + d + "\n"+isSuccess);

    retry2(new Action<int, int>(Add2), 45, 60);

    Console.ReadKey();
}

1

这样的代码片段应该能帮到您:

delegate int MyDelegate(int a, int b);
public int Add(int a, int b) {
    return a + b;
}
public void InvokeMethod(Delegate method, object[] param) {
    Console.WriteLine(method.DynamicInvoke(param));
}
public Form1() {       
    InitializeComponent();
    InvokeMethod(new MyDelegate(Add), new object[] { 1, 2 });
}

祝好运!


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