代码 1 delegate int DiscountDelegate();
2
3 class Program {
4 static void Main(string[] args) {
5 Calculator calc = new Calculator();
6 DiscountDelegate discount = null;
7 if (DateTime.Now.Hour < 12) {
8 discount = new DiscountDelegate(calc.Morning);
9 }
10 else if (DateTime.Now.Hour < 20) {
11 discount = new DiscountDelegate(calc.Afternoon);
12 }
13 else {
14 discount = new DiscountDelegate(calc.Night);
15 }
16 new ShoppingCart().Process(discount);
17 }
18 }
19 class Calculator {
20 public int Morning() {
21 return 5;
22 }
23 public int Afternoon() {
24 return 10;
25 }
26 public int Night() {
27 return 15;
28 }
29 }
30 class ShoppingCart {
31 public void Process(DiscountDelegate discount) {
32 int magicDiscount = discount();
33 // ...
34 }
35 }
正如你所见,在Calculator类中,我们为每个逻辑分支创建了一个方法。在Main方法中,我们创建一个Calculator实例和一个DiscountDelegate实例,并按照我们所期望的把它们整合在一起。
太棒了,我们不用担心Process方法中的逻辑了,我们只需要简单得回调我们定义的委托。记住!我们不关心委托是如何创建的(或什么时间),我们就像调用其他方法一样调用它。如你所见,
另一种理解委托的方法是,它延迟执行一个方法。Calculator方法在过去某个时间本选择,但不会执行,直到我们调用discount()的时候。现在看看我们的解决方案,
这里仍然存在一些丑陋的代码。在Calculator类中,我们可以用一个不同的方法来返回替代每个有返回值得方法吗?答案是肯定的,让我们把这些乱糟糟的代码合并起来。
代码 1 delegate int DiscountDelegate();
2
3 class Program {
4 static void Main(string[] args) {
5 new ShoppingCart().Process(new DiscountDelegate(Calculator.Calculate));
6 }
7 }
8 class Calculator {
9 public static int Calculate() {
10 int discount = 0;
11 if (DateTime.Now.Hour < 12) {
12 discount = 5;
13 }
14 else if (DateTime.Now.Hour < 20) {
15 discount = 10;
16 }
17 else {
18 discount = 15;
19 }
20 return discount;
21 }
22 }
23 class ShoppingCart {
24 public void Process(DiscountDelegate discount) {
25 int magicDiscount = discount();
26 // ...
27 }
28 }
这样子看起来更好点。你会注意到我们用一个静态的Calculate方法替换了所有原来的方法,在Main方法中也不用费心维护一个指向DiscountDelegate的引用。
现在你明白了所有关于委托的东西了吗?在2004年.NET1.1中可以这么说,但是很不幸的是,这种框架自那以后更加成熟了。
微软在.NET 2.0中引入了泛型,并提供了一个泛型委托:Action<T>。老实说,我认为它远不够用。后来在.NET 3.5中,
它为我们提供了一些我们不想定义的通用委托。他们扩展了Action,并添加了Func,二者唯一区别在于Func型方法有一个返回值而Action型方法没有。
这意味着我们不需要声明自己的DiscountDelegate,可以用Func<int>替代。为说明这些观点是如何工作的,我们来假设管理层又一次改变了
我们的逻辑,我们需要提供一些特殊的折扣。很简单,我们将给Calculate方法传入一个bool型值。
现在我们的委托签名变成Func<bool,int>。注意Calculate方法现在包含一个bool型参数,我们用一个bool值调用discount()。
代码 1 class Program {
2 static void Main(string[] args) {
3 new ShoppingCart().Process(new Func<bool, int>(Calculator.Calculate));
4 }
5 }
6
7 class Calculator {
8 public static int Calculate(bool special) {
9 int discount = 0;
10 if (DateTime.Now.Hour < 12) {
11 discount = 5;
12 }
13 else if (DateTime.Now.Hour < 20) {
14 discount = 10;
15 }
16 else if (special) {
17 discount = 20;
18 }
19 else {
20 discount = 15;
21 }
22 return discount;
23 }
24 }
25
26 class ShoppingCart {
27 public void Process(Func<bool,int> discount) {
28 int magicDiscount = discount(false);
29 int magicDiscount2 = discount(true);
30 }
31 }
好像还算不错,我们又省了一行代码,这样算结束了吗?当然没有,我们甚至能省掉类型判断。只要我们传递的方法有严格签名的委托,.NET允许我们完全忽略掉显式创建Func<bool,int>。
//因为Process期望的方法有一个bool型输入参数和返回一个int值,所以下面这句话是正确的
new ShoppingCart().Process(Calculator.Calculate);
至此,首先通过忽略自定义委托,我们省略了代码;然后排出了明确的创建Func委托。我们能继续压缩代码行吗?到此我们才完成此文的一半,答案显然是“能”。
正如你所见,我们完全删除了Calculator类的需求。你可以在打括号中添加任何其他方法中的逻辑。如果你在看它如何执行时有困难,那就把delegate(bool x)做为一个方法签名,
而不是一个关键字。设想这段代码在一个类里,delegate(bool x){return 5;}是一个完整的合法方法声明(我们确实有一个返回值),恰好delegate是一个保留字,
在这里,它让这个方法匿名。
至此,我确信现在你知道这里我们甚至能压缩更多的代码。顺利成章的,我们能忽略显式声明Func委托的需要;.NET让我们使用delegate关键字更方便。
1 class Program {
2 static void Main(string[] args) {
3 new ShoppingCart().Process(
4 delegate(bool x) { return x ? 10 : 5; }
5 );
6 }
7 }
当把.NET方法做为委托参数时或处理时间时,就能看到匿名方法的真正用处。之前,你会为你所关注的所有可能行为创建了一个方法,现在你仅需以内联的方式创建它们,
并可以避免污染你的命名空间。
代码
1 // 创建一个匿名比对方法
2 custs.Sort(delegate(Customer c1, Customer c2) {
3 return Comparer<int>.Default.Compare(c1.ID, c2.ID);
4 });
5
6 // 创建一个匿名事件
7 button1.Click += delegate(object o, EventArgs e) { MessageBox.Show("Click!"); };
Lambda 表达式
MSDN中写道:“Lambda 表达式”是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式树类型。你应当明白“用户创建委托”部分,
但什么是“表达式”呢?老实说,表达式和表达式树不在此为讨论范围内。现在我们唯一需要明白的是,表达式是.NET程序运行时表示数据或对象的代码(C#代码)。
引用Jon Skeet的话:“表达式树是一种表达逻辑,这样其他的代码可以查询的方法。当一个lambda表达式转换成一个表达式树,编译器不会发出了lambda表达式
的白细胞介素,它会发出白细胞介素这将会建立一个表达式树表示相同的逻辑。”
我们需要关注的是Lambda表达式替换匿名方法,和其他的特性。回顾我们最后例子,我们已经在一行代码里压缩了处理整个折扣算法的逻辑。
1 class Program {
2 static void Main(string[] args) {
3 new ShoppingCart().Process(
4 delegate(bool x) { return x ? 10 : 5; }
5 );
6 }
7 }
你相信我们能让这个更短吗?Lambda表达式用'=>'运算符表明什么参数传递给表达式。编译器进一步处理,允许我们忽略类型并自动替我们推断这些类型。如果你有2个或更多个参数,
你需要用圆括号:(x,y)=>。如果只有一个,你设置不需要这样:x=>。
代码
1 static void Main(string[] args) {
2 Func<bool, int> del = x => x ? 10 : 5;
3 new ShoppingCart().Process(del);
4 }
5 // 更短啦...
6 static void Main(string[] args) {
7 new ShoppingCart().Process(x => x ? 10 : 5);
8 }
就是这样子。x被推断为bool型,并且有返回值,因为Process接收一个Func<bool,int>。如果我们想实现像之前那样的完整代码块,我们只需要加上大括号。
代码
1 static void Main(string[] args) {
2 new ShoppingCart().Process( x => {
3 int discount = 0;
4 if (DateTime.Now.Hour < 12) {
5 discount = 5;
6 }
7 else if (DateTime.Now.Hour < 20) {
8 discount = 10;
9 }
10 else if(x) {
11 discount = 20;
12 }
13 else {
14 discount = 15;
15 }
16 return discount;
17 });
18 }
写在最后
使用与不使用大括号有一个重要的不同。当你用时,你创建一个“语句Lambda”,反之,它是"表达Lambda"。语句Lambda能执行多条语句(因此需要大括号),
但不能创建表达树。你可能只在使用IQueryable接口是遇到这个问题。下面的例子说明这个问题。
代码
1 List<string> list = new List<string>();
2 IQueryable<string> query = list.AsQueryable();
3 list.Add("one");
4 list.Add("two");
5 list.Add("three");
6
7