2013-07-17 100 views
10

任何人都可以提供匿名方法与lambda表达式之间的简洁区分吗?
匿名方法与lambda表达式

使用匿名方法:

private void DoSomeWork() 
{ 
    if (textBox1.InvokeRequired) 
    { 
     //textBox1.Invoke((Action)(() => textBox1.Text = "test")); 
     textBox1.Invoke((Action)delegate { textBox1.Text = "test"; }); 
    } 
} 

难道仅仅是一个正常的lambda表达式被强制转换为一个强类型的代表或有更多的它卧底。

我很清楚地知道,像一个强类型的代表跟随

UpdateTextDelegate mydelegate = new UpdateTextDelegate(MethodName) 

足以作为System.Delegate类型的参数,但匿名方法的想法是相当新的给我。

+1

你看过吗?还有其他问题? http://stackoverflow.com/questions/6008097/what-are-anonymous-methods-in-c –

+0

我完全理解什么是匿名方法/ lambda表达式和用法,我的问题是,他们有什么不同的方式从匿名代表或他们只是相同 – sarepta

+2

嗯,我只是写了一个****的文本加载,所以希望你会在那里找到你的答案,如果不让我知道:) –

回答

24

什么匿名方法?它真的是匿名的吗?它有名字吗?所有优秀的问题,让我们先从他们开始,随着我们的发展,继续努力学习lambda表达式。

当你这样做:

public void TestSomething() 
{ 
    Test(delegate { Debug.WriteLine("Test"); }); 
} 

究竟发生了什么?

编译器首先决定采取的方法,这是本的“身体”:

Debug.WriteLine("Test"); 

和分离出的是进入的方法。

两个问题,编译器现在必须回答:

  1. 我应该在哪里放置方法?
  2. 该方法的签名应该是什么样子?

第二个问题是容易回答。 delegate {部分回答了这个问题。该方法不带任何参数(delegate{之间没有任何东西),因为我们不关心它的名字(因此“匿名”的一部分),我们可以声明方法,例如:

public void SomeOddMethod() 
{ 
    Debug.WriteLine("Test"); 
} 

但为什么它做到了这一切?

让我们了解一下什么代表,如Action确实是。

一位代表是,如果我们暂时无视委托人的事实。NET实际上链表多的单一的“代表”,引用(指针)两件事情:

  1. 一个对象实例
  2. 对对象实例的方法

所以,用知识的第一段代码实际上可以改写成这样:现在

public void TestSomething() 
{ 
    Test(new Action(this.SomeOddMethod)); 
} 

private void SomeOddMethod() 
{ 
    Debug.WriteLine("Test"); 
} 

,这个问题是样稿iler没有办法知道Test实际上对它给出的委托是做什么的,并且因为委托的一半是对方法被调用的实例的引用,所以在上面的例子中我们不知道this,我们不知道将引用多少数据。例如,考虑上面的代码是否是一个真正巨大的对象的一部分,但是只是暂时存在的对象。还要考虑到Test会将该委托存储在长期存在的地方。 “漫长的时间”也会将自己与这个庞大的对象的生活联系在一起,同时长期保持这一点,可能并不好。

所以编译器不仅仅是创建一个方法,它还创建一个类来保存它。这回答第一个问题,我应该在哪里放?

上面的代码如下因此可以改写:

public void TestSomething() 
{ 
    var temp = new SomeClass; 
    Test(new Action(temp.SomeOddMethod)); 
} 

private class SomeClass 
{ 
    private void SomeOddMethod() 
    { 
     Debug.WriteLine("Test"); 
    } 
} 

也就是说,在这个例子中,什么样的匿名方法的真正意义。

事情变得更毛茸茸的一点,如果你开始使用本地变量,考虑下面的例子:

public void Test() 
{ 
    int x = 10; 
    Test(delegate { Debug.WriteLine("x=" + x); }); 
} 

这是引擎盖下会发生什么,或者至少东西非常接近:

public void TestSomething() 
{ 
    var temp = new SomeClass; 
    temp.x = 10; 
    Test(new Action(temp.SomeOddMethod)); 
} 

private class SomeClass 
{ 
    public int x; 

    private void SomeOddMethod() 
    { 
     Debug.WriteLine("x=" + x); 
    } 
} 

编译器创建一个类,将该方法需要的所有变量提取到该类中,并重写所有对局部变量的访问以访问匿名类型的字段。

类的名称和方法,是有点奇怪,让我们问LINQPad会是怎样:

void Main() 
{ 
    int x = 10; 
    Test(delegate { Debug.WriteLine("x=" + x); }); 
} 

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

如果我问LINQPad输出这一计划的IL(中间语言),我得到这个:

// var temp = new UserQuery+<>c__DisplayClass1(); 
IL_0000: newobj  UserQuery+<>c__DisplayClass1..ctor 
IL_0005: stloc.0  // CS$<>8__locals2 
IL_0006: ldloc.0  // CS$<>8__locals2 

// temp.x = 10; 
IL_0007: ldc.i4.s 0A 
IL_0009: stfld  UserQuery+<>c__DisplayClass1.x 

// var action = new Action(temp.<Main>b__0); 
IL_000E: ldarg.0  
IL_000F: ldloc.0  // CS$<>8__locals2 
IL_0010: ldftn  UserQuery+<>c__DisplayClass1.<Main>b__0 
IL_0016: newobj  System.Action..ctor 

// Test(action); 
IL_001B: call  UserQuery.Test 

Test: 
IL_0000: ldarg.1  
IL_0001: callvirt System.Action.Invoke 
IL_0006: ret   

<>c__DisplayClass1.<Main>b__0: 
IL_0000: ldstr  "x=" 
IL_0005: ldarg.0  
IL_0006: ldfld  UserQuery+<>c__DisplayClass1.x 
IL_000B: box   System.Int32 
IL_0010: call  System.String.Concat 
IL_0015: call  System.Diagnostics.Debug.WriteLine 
IL_001A: ret   

<>c__DisplayClass1..ctor: 
IL_0000: ldarg.0  
IL_0001: call  System.Object..ctor 
IL_0006: ret   

在这里你可以看到类的名称为UserQuery+<>c__DisplayClass1,和方法的名称是<Main>b__0。我在产生这段代码的C#代码中进行了编辑,在上面的例子中,LINQPad除了IL之外没有产生任何东西。

小于和大于号的符号可以确保您不会无意中创建与编译器为您生成的类型和/或方法相匹配的类型和/或方法。

所以这基本上是一个匿名方法。

那么这是什么?

Test(() => Debug.WriteLine("Test")); 

那么,在这种情况下,它是相同的,这是一个生成匿名方法的捷径。

您可以通过两种方式写:

() => { ... code here ... } 
() => ... single expression here ... 

在其第一种形式,你可以写你会在一个正常的方法体执行代码。在第二种形式中,您可以写一个表达式或语句。

然而,在这种情况下,编译器会将此:

() => ... 

相同的方式,这样的:

delegate { ... } 

他们仍然匿名方法,它只是在() =>语法是达到它的捷径。

所以,如果它是达到它的捷径,为什么我们拥有它?

嗯,它使生活变得更加容易一些,它被添加的目的是LINQ。

考虑以下LINQ声明:

var customers = from customer in db.Customers 
       where customer.Name == "ACME" 
       select customer.Address; 

此代码改写如下:

var customers = 
    db.Customers 
     .Where(customer => customer.Name == "ACME") 
     .Select(customer => customer.Address"); 

如果你使用的delegate { ... }语法,你将有return ...等改写表达式他们看起来更时髦。因此,添加lambda语法是为了让我们的程序员在编写上述代码时更轻松。

那么表达式是什么?

到目前为止,我还没有Test是如何被定义的,但让我们定义Test上面的代码:

public void Test(Action action) 

这应该足够了。它说“我需要一个委托,它是Action类型的(不带参数,不返回任何值)”。

然而,微软还增加了一个不同的方法来定义这个方法:

public void Test(Expression<Func<....>> expr) 

注意,我放弃了部分在那里,....部分,让我们回到那个。

这个代码,这个调用成对:

Test(() => x + 10); 

实际上不会传递的委托,也没有任何可以(立即)调用。相反,编译器将这段代码重写的东西类似(但不是在所有喜欢)下面的代码:

var operand1 = new VariableReferenceOperand("x"); 
var operand2 = new ConstantOperand(10); 
var expression = new AdditionOperator(operand1, operand2); 
Test(expression); 

基本上编译器将建立一个Expression<Func<...>>对象,包含对变量的引用,文字值,使用的操作符等,并将该对象树传递给该方法。

为什么?

那么,考虑上面的db.Customers.Where(...)部分。

如果不是从数据库中将所有客户(及其所有数据)下载到客户端,而是将所有客户端循环遍历所有客户端,找出哪个客户有正确的名称等等,它会不会很好?要求数据库一次找到那个单一的,正确的客户?

这就是表达背后的目的。实体框架,Linq2SQL或任何其他此类支持LINQ的数据库层将采用该表达式,对其进行分析,将其分开,并编写一份格式正确的SQL以针对数据库执行。

这可能是从来没有如果我们仍然将它委托给包含IL的方法,它只能做到这一点,因为两件事情的:

  1. 允许适合于Expression<Func<...>> lambda表达式语法是有限的(没有语句等)
  2. 拉姆达语法,而不大括号,这告诉编译器,这是一个代码简单的形式

所以,让我们总结一下:

  1. 匿名方法实际上并不全是匿名的,它们最终只是一种命名类型,只有一种命名方法,只有你不必自己命名这些东西
  2. 这是很多编译器魔术,周围,​​这样你就不必
  3. 表达式和委托是看一些同样的事情
  4. 表达式是用于那些想知道代码做什么,以及如何框架两种方式,让他们可以使用这些知识来优化过程(如编写SQL语句)
  5. 代表的意思是这是框架只关心能够调用的方法

脚注:

  1. 这样一个简单的表达....部分是为返回值的你的类型从表达。 () => ... simple expression ...只允许表达式,即返回值的东西,它不能是多个语句。因此,有效的表达式类型如下:Expression<Func<int>>,基本上,表达式是返回整数值的函数(方法)。

    请注意,“返回值的表达式”是Expression<...>参数或类型的限制,但不是委托的限制。这完全是合法的代码,如果参数类型的TestAction

    Test(() => Debug.WriteLine("Test")); 
    

    显然,Debug.WriteLine("Test")不返回任何东西,但这是合法的。如果方法Test需要表达式但是,它不会,因为表达式必须返回一个值。

+2

术语“匿名”实际上是一个很好的这个期限。考虑一下,如果有人捐出了一大笔钱来做一件好事。很明显,这个人有一个名字,这只是不知道人们得到的钱。在这种情况下,方法的名称不为人知,但实际上有一个名称。 –

+0

优秀的话语,但当试图将这些表达式作为处理对象时,除了语法糖之外,还存在细微差别。如果您尝试将匿名委托传递给仅使用“Expression >”的方法,会发生什么? –

+0

你不能那样做。您会收到一个编译器错误,“匿名方法表达式不能转换为表达式树”。这是来自'Test(delegate {return 10;});'当方法被声明为'public void Test(Expression > expr)'。请注意,我并不是说没有我没有涉及到的东西,所以如果你能找到一些我很乐意扩展这些的东西,但是将一个委托传给一个需要表达式的方法是没有用的,走。 –

1

准确地说,你所说的“匿名委托”实际上是一种匿名方法。

那么,lambdas和匿名方法都只是语法糖。编译器会为你生成至少一个'普通'方法,尽管有时(在关闭的情况下)它会生成一个嵌套类,其中不再有更长的匿名方法。

5

您应该注意一个细微差别。考虑以下查询(使用谚语NorthWind)。

Customers.Where(delegate(Customers c) { return c.City == "London";}); 
Customers.Where(c => c.City == "London"); 

第一个使用匿名委托,第二个使用lambda表达式。如果你评估两者的结果,你会看到同样的结果。然而,看着生成的SQL,我们会看到完全不同的故事。第一产生

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] 
FROM [Customers] AS [t0] 

,而第二在第一种情况下产生

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] 
FROM [Customers] AS [t0] 
WHERE [t0].[City] = @p0 

通告,where子句不传递到数据库中。为什么是这样?编译器能够确定lambda表达式是一个简单的单行表达式,可以保留为表达式树,而匿名委托不是lambda表达式,因此不能作为Expression<Func<T>>进行包装。因此,在第一种情况下,Where扩展方法的最佳匹配方式是扩展IEnumerable的方法,而不是需要Expression<Func<T, bool>>的IQueryable版本。

此时,匿名代理几乎没有用处。它更加冗长而且不太灵活。一般来说,我会建议您始终在匿名代理语法上使用lambda语法,并选择可理解性和句法简洁性。