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要复杂的多。

  

 

posted @ 2013-05-28 14:31  史蒂芬King  阅读(363)  评论(0编辑  收藏  举报