新建Action()和lambda表达式有什么区别?

49

所以当我写下这样的东西时

Action action = new Action(()=>_myMessage = "hello");

Refactor Pro!将这标记为多余的委托创建并允许我将其缩短为

Action action = () => _myMessage="hello";

通常这很有效。通常是这样的,但不总是。例如,Rhino Mocks有一个名为Do的扩展方法:

IMethodOptions<T> Do(Delegate action);

在这里,传递第一个版本是有效的,但第二个版本不起作用。在幕后到底发生了什么?


4
你的第二个代码块不能编译。我收到了这个消息:“无法将lambda表达式分配给隐式类型的局部变量”。但是,如果我用“Action”替换“var”,它就可以编译。 - Aaron Hoffman
1
是的,你说得对,它不能被分配给一个隐式类型变量,我会进行编辑。 - George Mauer
3个回答

64

第一个版本实际上是在执行:

Action tmp = () => _myMessage = "hello";
var action = new Action(tmp);
你遇到的问题是编译器需要知道将lambda表达式转换成哪种类型的委托(或表达式树)。这就是为什么会出现这种情况的原因:
var action = () => _myMessage="hello";

实际上无法编译 - 它可以是任何不带参数并且没有返回值或者返回值和_myMessage(可能是string)相同的委托类型。例如,以下所有内容都是有效的:

Action action = () => _myMessage="hello";
Func<string> action = () => _myMessage="hello";
MethodInvoker action = () => _myMessage="hello";
Expression<Action> = () => _myMessage="hello";
// etc

如果使用 var 声明 action,C# 编译器如何确定它的类型?

在调用方法时(例如 Rhino Mocks 的示例),最简单的解决方法是进行强制类型转换:

methodOptions.Do((Action) (() => _myMessage = "hello"));

3
VB.Net可以通过根据使用情况动态生成委托类型来解决这个问题。因为VB已经区分了无返回值和有返回值的函数(Sub和Function),所以使区分变得更容易。 - JaredPar
5
如果使用var声明一个action类型,C#编译器如何确定它指的是哪种类型?简单来说,函数类型应该是一类结构类型,而非那些有名称的委托类型。同时,引用的代码应该被标注为引用代码。但我猜现在这些也无法改变了 :). - MichaelGG
1
我认为您需要在lambda周围添加一个额外的括号来执行此类转换。 - H.B.
@H.B. 完成 - 每当我转换 lambda 表达式时,我总是惊讶于需要多少括号 :) - Jon Skeet
@JonSkeet:这就是为什么我在这些情况下使用 new Action(() => ...) - H.B.

9
你是否验证过第二行代码确实可以编译?事实上,它不应该编译通过,因为C#不支持将lambda表达式分配给隐式类型变量(CS0815)。然而,这行代码在VB.Net中可以工作,因为它支持匿名委托的创建(从VB 9.0开始)。
与第二行代码的原因相同,Rhino Mocks版本也无法编译。 C#不会自动推断lambda表达式的类型。Lambda表达式必须在可以确定其所需的委托类型的上下文中使用。第一行代码很好地工作,因为其意图类型非常明确:Action。而Rhino Mocks版本不可行,因为Delegate更像是一个抽象的委托类型。它必须是一个具体的委托类型,例如Action或Func。
如果您想详细讨论此主题,请阅读Eric Lippert在此主题上的博客文章:http://blogs.msdn.com/ericlippert/archive/2007/01/11/lambda-expressions-vs-anonymous-methods-part-two.aspx

1

动作是一种特殊类型的委托。因此,如果您使用lambda表达式,由于存在许多(Action、Func等),将不理解您想要使用哪种类型。

为了克服需要强制转换的问题(在大多数情况下速度较慢),您可以将基本函数的参数从Delegate更改为Action:

IMethodOptions<T> Do(Action action);

这样做可以同时使用两个语句,而不会有任何区别:
Action action = new Action(()=>_myMessage = "hello"); 
Action action = () => _myMessage="hello";

如果不可能,那么我建议使用new Action(() => {})而不是转换,这样会更快。
请阅读以下链接以获取有关Action和Delegate的更多信息:https://learn.microsoft.com/en-gb/dotnet/api/system.action?view=netframework-4.7.1#definition

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