CSharpThinking---委托相关(二)
学而不思则罔,思而不学则殆。
本章主要内容是介绍委托的发展历程及不同阶段的优缺点。文章最后给出了一些概念的解释。
关于委托:
C#1:用特定的签名实现委托,委托相当于方法的“指针”或者叫方法的引用。
不足:1.如果想实现一个小委托也要创建一个完整的新方法。
2.方法名过长。
#region C#1 Button btn = new Button(); btn.Click += new EventHandler(tb_Click); .... void tb_Click(object sender, EventArgs e) { // throw new NotImplementedException(); } #endregion
C#2:提供了匿名方法,泛型委托类型Action<T>. 以及带返回值的Predicate<T>(T obj)
优点:简化委托delegate(first,second){ return first < second}; 如果不需要参数值,可使用匿名委托 delegate{...} 。
Button tb = new Button(); tb.Click += delegate(object sender, EventArgs e) { Console.WriteLine(string.Format("非匿名委托,命令源{0}.", sender)); }; tb.Click += delegate { Console.WriteLine("匿名委托,无需参数。"); };
注:匿名方法是C#2以被捕捉的变量的形式来实现,在别的地方称为闭包的一个特性。 ---C#InDepth
C#3:提供Lambda,表达式树和Linq
1.Lambda表达式:(显式参数列表)=>{...}
1.1.Lambda表达式发展历程
历程 | 表达式 | 备注 |
1.匿名方法 | delegate(string Text){return Text.Length;} | 匿名方法 |
2.转换为Lambda表达式 | (string Text) => {return Text.Length;} | 转换 |
3.单个表达式 | (string Text) => Text.Length; | 去除不必要的大括号 |
4.让编译器推断隐式参数类型 | (Text) => Text.Length; | 编译器能猜测的出来 |
5.去除不必要的括号 | Text => Text.Length; | 最简形式,但有限制 |
1.2.Lambda表达式针对List的操作: 编译器执行自动转换及缓存Lambda表达式
1 public class Item { public int Year { get; set; } } 2 3 public class Lambda表达式 4 { 5 public Lambda表达式() 6 { 7 List<Item> Items = new List<Item>(); 8 9 Items.FindAll(item => item.Year < 1960); // Lambda表达式 10 11 Items.FindAll(new Predicate<Item> (SomeAutoGenerateName)); // 编译器转化后的Lambda表达式,编译器会自动缓存Lambda表达式 12 } 13 14 private static bool SomeAutoGenerateName(Item item) 15 { 16 return item.Year < 1960; 17 } 19 }
1.3. 延迟加载
恰当的时间真正做转换,使数据能在适当的位置以一种“Just-In-Time”方式提供.
1 var col = Enumerable.Range(0,10).Reverse();// 实际并未执行任何操作,执行时间为O(1); 2 3 foreach(var element in col) 4 { 5 Console.WriteLine(element); // 此时调用才会真正执行....Reverse(), 前期只是准备。即,延迟执行。 6 }
2.表达式树 :代码作为数据,树形表达式集合
2.1.表达式树的工作方式:我们在程序中创建了一些逻辑块,将其表示成普通对象,然后要求框架将所有的东西都编译成可执行的“真实”的代码。可能永远也不会这样用,但有助于理解Linq的工作方式。
1 /// <summary> 2 /// 简洁版表达式树 3 /// </summary> 4 void SimpleExpressionTree() 5 { 6 Expression<Func<String, String, bool>> exp = (x, y) => x.StartsWith(y); 7 var compiled = exp.Compile(); 8 Console.WriteLine(compiled("A", "B")); 9 } 10 11 /// <summary> 12 /// 简洁版表达式树用lambdaExpression转换的实例 13 /// </summary> 14 /// <remarks> 15 /// 目的:了解表达式树底层实现方式,便于了解工作流程。 16 /// </remarks> 17 void ComplexExpressionTree() 18 { 19 // 1.构造方法调用各个部件 20 MethodInfo method = typeof(string).GetMethod("StartWith", new[] { typeof(string) }); 21 var target = Expression.Parameter(typeof(string), "x"); 22 var methodArg = Expression.Parameter(typeof(string), "y"); 23 Expression[] methodArgs = new[] { methodArg }; 24 // 2.创建调用表达式 25 Expression call = Expression.Call(target, method, methodArgs); 26 var lambdaParameters = new[] { target, methodArg }; 27 var lambda = Expression.Lambda<Func<string, string, bool>>(call, lambdaParameters); 28 // 3.将Call转换成Lambda表达式 29 var compiled = lambda.Compile(); 30 Console.WriteLine(compiled("A", "B")); 31 }
2.2.表达式树常用在Linq,但不总是这样。 Linq = 表达式树+Lambda+扩展方法
Bjarne Stroustrup :“我不会创建这样的工具,它所能做的,都是我可以想象得到的。”
2.3.表达式树是构建动态语言的核心。
重要概念解释:
一. 闭包:一个函数除了能通过提供给它的参数与环境的互动外,还能同环境进行更大程度的互动。
例子:
1 public delegate void MethodInvoker(); 2 void EnClosingMethod() 3 { 4 int outerVariable = 5; // 外部变量,未捕获。匿名方法没有用它,所以未捕获。 5 string capturedVariable = "captured"; // 被匿名方法捕获的外部变量。 6 7 if (DateTime.Now.Hour == 23) 8 { 9 int normalLocalVariable = DateTime.Now.Minute; // 普通方法的局部变量,作用域内没有匿名方法。 10 Console.WriteLine(normalLocalVariable); 11 } 12 13 MethodInvoker x = delegate() 14 { 15 string anonLocal = "Local to anonymous method"; // 匿名方法的局部变量,只有在匿名方法被调用时才存在于正在执行的栈帧(Frame)中。 16 Console.WriteLine(capturedVariable + anonLocal); // 捕获外部变量 17 }; 18 19 x(); 20 }
被捕捉的变量的生存期延长,局部变量并不总是“局部”。
1 static MethodInvoker CreateDelegateInstance() 2 { 3 int counter = 5; // 不要认为counter的作用域仅仅在CreateDelegateInstance中。 4 MethodInvoker x = delegate() 5 { 6 Console.WriteLine(counter); 7 counter++; 8 }; 9 x(); 10 return x; 11 } 12 13 void Test() 14 { 15 MethodInvoker invoker = CreateDelegateInstance();// 整个CreateDelegateInstance在堆上而非栈上,故能保存counter值。 16 invoker(); 17 invoker(); 18 19 /* Output: 20 * 5 21 * 6 22 * 7 23 * */ 24 }
使用多个委托来捕获多个变量的实例:
1 public static void MutiDelegate() 2 { 3 List<MethodInvoker> list = new List<MethodInvoker>(); 4 5 for (int index = 0; index < 5; index++) 6 { 7 int counter = index * 10; 8 list.Add(delegate() 9 { 10 Console.WriteLine(counter);// 若改成index ,则输出全部为5 11 counter++; 12 }); 13 } 14 15 foreach (var item in list) 16 { 17 item(); // 执行委托实例 18 } 19 20 list[0]();// Output: 1 21 list[0]();// Output: 2 22 list[1]();// Output: 11 23 24 /* 25 * counter在循环内部声明,所以会生成5个counter副本。 26 * 而index仅仅声明了一次,所以有唯一一个副本。 27 * */ 28 }
捕获变量小结:
1.捕获的是变量,而不是创建委托实例时它的值。
2.捕获的变量的生存期被延长了,至少和捕捉它的委托一样长。
3.多个委托可以捕获同一个变量。
4.必要时创建额外的类型来保存捕获变量。
5.如果用或不用捕获变量同样简单及简洁,那就不用。
6.考虑垃圾回收问题,尽量不要延长捕获变量的生存期。
二. C#3 编译器推断Lambda表达式中的参数类型,相比C#2要复杂的多。
作者:Stephen Cui
出处:http://www.cnblogs.com/cuiyansong
版权声明:文章属于本人及博客园共有,凡是没有标注[转载]的,请在文章末尾加入我的博客地址。
如果您觉得文章写的还不错,请点击“推荐一下”,谢谢。