CSharpThinking

本文介绍了C#中委托的发展历程,从最初的特定签名到引入Lambda表达式和表达式树,展示了不同版本间的语法改进及其带来的优势。同时,文中还深入探讨了闭包的概念及其在C#中的应用。

学而不思则罔,思而不学则殆。

本章主要内容是介绍委托的发展历程及不同阶段的优缺点。文章最后给出了一些概念的解释。

 

关于委托:

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

  

 


<script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值