编写一个接受 lambda 表达式的方法

26

我有一个方法,其签名如下:

 void MyMethod(Delegate d){};
 void MyMethod(Expression exp){};
 void MyMethod(object obj){};

然而,这个无法编译:

MyMethod((int a) => a)

以下是错误信息:

"Cannot convert lambda expression to type 'object' because it is not a delegate type"

为什么这个不起作用?

编辑:我知道这个可以工作。在这种情况下,编译器将lambda表达式编译为委托。

void MyMethod(Func<int, int> d){};

此致敬礼,


3
请注意,每当出现编译错误时,请阅读(并发布)错误消息。 - Greg D
@SharePoint 新手:请查看我的更新帖子。现在应该可以解决你的错误了。 - Noldorin
6个回答

21

因为类型System.Delegate并不是“Delegate”,它只是基类。你需要使用带有正确签名的委托类型。将您的方法定义如下:

void MyMethod(Func<int, int> objFunc)

编辑:

MyMethod(object) 不起作用是因为 lambda 表达式本身没有类型,它的类型是从分配给它的位置的类型推断出来的。因此,object 也无法工作。你必须使用具有正确签名的委托类型。


我知道这个可以工作,但我想知道为什么其他签名不起作用。 - SharePoint Newbie
然后请阅读我所写的内容。System.Delegate 不是委托,lambda 表达式无法转换为 System.Delegate。事实上,lambda 表达式本身没有类型,它们从分配给它们的变量的类型中获取其类型,因此 object 也不起作用。 - Maximilian Mayerl
但是我自己指定了类型。它并没有被隐式推断出来,"(int a) => a;"。你能否解释一下,Lambda表达式本身没有类型这个概念吗? - SharePoint Newbie
1
请查看C#语言规范版本3.0,第6.5节匿名函数转换。 - Maximilian Mayerl

15
void MyMethod(Action<int> lambdaHereLol)
{
    lambdaHereLol(2);
}

正在使用中:

var hurrDurr = 5;
MyMethod(x => Console.Write(x * hurrDurr));

C#是一种静态类型语言。编译器需要知道它处理的所有内容的类型。Lambda有点难以确定,有时编译器无法弄清楚。在上面的示例中,如果MyMethod接受一个对象,编译器无法确定x是一个int(我的示例很简单,但并不意味着它不能更复杂和更难确定)。因此,我必须更明确地定义接受我的lambda的方法。


1
还有谁老是混淆“cloture”和“closure”? - user1228
1
"C#是一种静态类型语言。编译器需要知道它处理的所有内容的类型。" 这是正确的,但其余部分并不完全正确。有很多静态类型语言可以为您推断类型签名,例如F#。 - rgrinberg
@rgrinberg:除了编译器会使用推断来在编译时确定类型之外,我不确定你试图做出的区别是什么。 - user1228
1
我在谷歌上搜索了10分钟才找到这个。谢谢! - user1873073

3
一个像这样的lambda表达式 (int a) => a 可以适用于任何接收一个 int 类型参数并返回一个 int 类型的委托。 Func<int, int> 只是一个例子,你也可以很容易地使用 delegate int Foo(int x); 声明一个自己的委托类型。实际上,这个lambda表达式还可以适用于一个接收int类型参数并返回double类型的委托,因为lambda表达式的结果(a)可以隐式转换为double
为了使 lambda 表达式可赋值给其适用的所有委托类型,它本身并没有固有的类型。相反,它会根据您正在使用的委托的类型而变化,只要那是可能的。(当然,(int a) => a 不能分配给 Func<byte, byte>。)
虽然 Func<int, int> 和我定义的 Foo 委托都可以转换为 Delegate,但是lambda表达式不能直接转换为 Delegate,因为不清楚它的实际签名是什么。在 Delegate d = (int a) => a 之后,dFoo,还是 Func<int, int>,甚至是 Func<int, double>?这些都是有效的可能性,编译器不知道你的意图。它可以猜测最佳解,但 C# 不是那种会做这种猜测工作的语言。这也是为什么你不能像 var = (int a) => a 这样做。
我认为编译器给出的 Delegate d = (int a) => a; 的错误消息非常不清楚:

Cannot convert lambda expression to type 'System.Delegate' because it is not a delegate type

直觉上你会认为 Delegate 是一个委托类型,但事实并非如此。:)

2

试试这个:

void MyMethod(Action<int> func) { }

你需要将一个强类型委托作为方法的参数。其他调用失败的原因是因为C#编译器不允许你将lambda表达式传递给期望Object的方法,因为在所有情况下,lambda表达式并不一定总是一个委托。同样的规则适用于将lambda表达式作为Delegate传递。
当你将lambda表达式传递给像我上面展示的函数时,编译器可以安全地假设你想要将lambda表达式转换为特定的委托类型,并执行此操作。

我知道这个可以工作,我想知道为什么其他签名不起作用。 - SharePoint Newbie

0

这只是编译器的本质,当您将委托对象作为类型为Delegate的参数传递时,您需要显式地将其转换为Delegate。事实上,Lambda表达式在这种情况下甚至更加复杂,因为它们不能隐式转换为委托。

您需要进行双重转换,如下所示:

MyMethod((Delegate)(Func<int, int>)((int a) => a));

这当然对应于方法签名:

void MyMethod(Delegate d);

根据您的情况,您可能希望定义一个类型为Func<int>的参数,而不是Delegate(尽管我会犹豫添加重载,因为它会增加不必要的复杂性)。

是的,因为正如我所写的那样,System.Delegate并不是委托。它只是委托的基础类型。 - Maximilian Mayerl
1
哎呀,你是对的。我错过了必须先进行委托类型转换的步骤。这有点揭示了C#编译器如何将lambda表达式隐式转换为委托,并将其作为参数传递的过程。在这里看到的是手动完成了所有操作。 - Noldorin

0
这个失败的原因与表达式“object del = (int a) => a”或者“var del = (int a) => a”失败的原因相同。你可能认为编译器可以根据你显式给出的参数类型来确定lambda表达式的类型,但即使知道表达式需要一个整数并返回一个整数,也有许多委托类型可以转换成。Func委托类型是用于通用函数的最常用类型,但这只是一种约定,编译器并不知道。
你需要做的是将lambda表达式强制转换为具体的委托类型,以便编译器选择Delegate重载,可以使用正常的强制转换语法(Func)((int a)=>a),也可以使用委托构造函数语法new Func((int a)=>a)。
此外,通常不要使用未经过类型化的Delegate类,除非您需要根据接受的参数数量以不同的方式调用某些内容。对于回调等事情,几乎总是最好接受Func或Action。

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