什么是匿名方法?它真的是匿名的吗?它有名字吗?所有优秀的问题,让我们先从他们开始,随着我们的发展,继续努力学习lambda表达式。
当你这样做:
public void TestSomething()
{
Test(delegate { Debug.WriteLine("Test"); });
}
究竟发生了什么?
编译器首先决定采取的方法,这是本的“身体”:
Debug.WriteLine("Test");
和分离出的是进入的方法。
两个问题,编译器现在必须回答:
- 我应该在哪里放置方法?
- 该方法的签名应该是什么样子?
第二个问题是容易回答。 delegate {
部分回答了这个问题。该方法不带任何参数(delegate
和{
之间没有任何东西),因为我们不关心它的名字(因此“匿名”的一部分),我们可以声明方法,例如:
public void SomeOddMethod()
{
Debug.WriteLine("Test");
}
但为什么它做到了这一切?
让我们了解一下什么代表,如Action
确实是。
一位代表是,如果我们暂时无视委托人的事实。NET实际上链表多的单一的“代表”,引用(指针)两件事情:
- 一个对象实例
- 对对象实例的方法
所以,用知识的第一段代码实际上可以改写成这样:现在
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的方法,它只能做到这一点,因为两件事情的:
- 允许适合于
Expression<Func<...>>
lambda表达式语法是有限的(没有语句等)
- 拉姆达语法,而不大括号,这告诉编译器,这是一个代码简单的形式
所以,让我们总结一下:
- 匿名方法实际上并不全是匿名的,它们最终只是一种命名类型,只有一种命名方法,只有你不必自己命名这些东西
- 这是很多编译器魔术,周围,这样你就不必
- 表达式和委托是看一些同样的事情
- 表达式是用于那些想知道代码做什么,以及如何框架两种方式,让他们可以使用这些知识来优化过程(如编写SQL语句)
- 代表的意思是这是框架只关心能够调用的方法
脚注:
这样一个简单的表达....
部分是为返回值的你的类型从表达。 () => ... simple expression ...
只允许表达式,即返回值的东西,它不能是多个语句。因此,有效的表达式类型如下:Expression<Func<int>>
,基本上,表达式是返回整数值的函数(方法)。
请注意,“返回值的表达式”是Expression<...>
参数或类型的限制,但不是委托的限制。这完全是合法的代码,如果参数类型的Test
是Action
:
Test(() => Debug.WriteLine("Test"));
显然,Debug.WriteLine("Test")
不返回任何东西,但这是合法的。如果方法Test
需要表达式但是,它不会,因为表达式必须返回一个值。
你看过吗?还有其他问题? http://stackoverflow.com/questions/6008097/what-are-anonymous-methods-in-c –
我完全理解什么是匿名方法/ lambda表达式和用法,我的问题是,他们有什么不同的方式从匿名代表或他们只是相同 – sarepta
嗯,我只是写了一个****的文本加载,所以希望你会在那里找到你的答案,如果不让我知道:) –