匿名方法是C# 2.0中简化委托模型的一种语法糖。Lambda表达式是C# 3.0新增的语法特性,其在匿名方法的基础上更进一步,但其本质都是相同的,我们通过一段代码来分析对比这个语言特性。它们分别是在C#2.0与C# 3.0中的主要写法。
1 delegate void DelWithoutParam(); 2 delegate void DelWithParams(int intparam, string strparam); 3 delegate string DelParamRetVal(int param); 4 5 class Program 6 { 7 public DelWithoutParam delTestNoParam; 8 public DelWithParams delTestWithParam; 9 public DelParamRetVal delTestParamRetVal; 10 11 public event DelWithoutParam evtTestNoParam; 12 public event DelWithParams evtTestWithParam; 13 14 static void Main(string[] args) 15 { 16 Program p = new Program(); 17 //订阅处理函数 18 p.SubEvent(); 19 p.ShowVarible(); 20 21 Console.Read(); 22 //依次触发委托与事件 23 p.delTestNoParam(); 24 p.delTestWithParam(1, "ParamInDelobj"); 25 //只会返回第二个调用函数的返回值 26 Console.WriteLine(p.delTestParamRetVal(10086)); 27 28 p.evtTestNoParam(); 29 p.evtTestWithParam(1, "ParamInEvt"); 30 31 //Console.ReadLine(); 32 Thread.Sleep(100000); 33 } 34 35 public void SubEvent() 36 { 37 //不具有参数的委托声明,在delegate后面加上"()" 38 delTestNoParam += delegate() 39 { 40 Console.WriteLine("del - 匹配不带参数的委托的方法被在匿名函数中调用"); 41 }; 42 43 evtTestNoParam += delegate() 44 { 45 Console.WriteLine("evt - 匹配不带参数的委托的方法被在匿名函数中调用"); 46 }; 47 48 //匿名方法出现之前调用委托处理 49 evtTestNoParam += new DelWithoutParam(Program_evtTestNoParam); 50 51 //lambda表达式 52 evtTestNoParam += () => Console.WriteLine("evt - 匹配不带参数的委托的方法被在lambda表达式中调用"); 53 54 //有返回值的委托 55 delTestParamRetVal += delegate(int p) 56 { 57 return "有返回值委托,在匿名函数中处理:" + p.ToString(); 58 }; 59 60 delTestParamRetVal += (q) => { return "有返回值委托,在lambda表达式中处理:" + q.ToString(); }; 61 } 62 63 public void ShowVarible() 64 { 65 delTestWithParam += delegate(int int_p, string str_p) 66 { 67 Console.WriteLine("del - 匹配参数int:{0},string:{1}的委托的方法被在匿名函数中调用", int_p, str_p); 68 }; 69 70 evtTestWithParam += delegate(int int_p, string str_p) 71 { 72 Console.WriteLine("evt - 匹配参数int:{0},string:{1}的委托的方法被在匿名函数中调用", int_p, str_p); 73 }; 74 75 delTestWithParam += delegate 76 { 77 Console.WriteLine("del - 匹配任何参数的委托的方法被在匿名函数中调用"); 78 }; 79 80 //lambda表达式 81 evtTestWithParam += (i, s) => Console.WriteLine("evt - 匹配参数int:{0},string:{1}的委托的方法被在lambda表达式中调用", i, s); 82 } 83 84 /*辅助函数*/ 85 void Program_evtTestNoParam() 86 { 87 Console.WriteLine("evt - 传统方式调用:匹配不带参数的委托的方法被在函数中调用"); 88 } 89 }
这篇文章主要介绍的一个问题是匿名方法中使用类成员或局部变量以及对匿名函数外部局部变量或参数的影响(对于lambda表达式同理,代码中有体现)。上述代码改造一下如下,我们通过这段代码来分析这个特性。
下列代码中需要注意的是匿名函数捕获变量的一个特殊情况(加粗代码),即局部变量的初始化问题,通过代码中两个对比应该可以很清楚的看出原因。
1 delegate void DelWithoutParam(); 2 delegate void DelWithParams(int intparam, string strparam); 3 4 class Program 5 { 6 public DelWithoutParam delTestNoParam; 7 public DelWithParams delTestWithParam; 8 9 public string classmember = "这是类成员"; 10 11 static void Main(string[] args) 12 { 13 Program p = new Program(); 14 //订阅处理函数 15 p.SubEvent(); 16 p.ShowVarible(); 17 18 Console.Read(); 19 //依次触发委托与事件 20 p.delTestNoParam(); 21 p.delTestWithParam(1, "ParamInDelobj"); 22 23 //Console.ReadLine(); 24 Thread.Sleep(100000); 25 } 26 27 public void SubEvent() 28 { 29 //匿名函数 - 调用成员变量示例 30 delTestNoParam += delegate() 31 { 32 Console.WriteLine("匿名函数中调用成员变量:{0}", classmember); 33 }; 34 35 //lambda表达式 - 调用成员变量示例 36 delTestNoParam += () => Console.WriteLine("lambda表达式中调用成员变量:{0}", classmember); 37 38 int pd = 1; 39 int pl = 2; 40 //匿名函数 - 调用局部变量 41 delTestNoParam += delegate() 42 { 43 //由于匿名函数内部使用了局部变量,局部变量的生命周期增长,而不是立即被垃圾回收。 44 Console.WriteLine("匿名函数中调用局部变量:{0}", pd); 45 }; 46 //lambda表达式 - 调用局部变量 47 delTestNoParam += () => Console.WriteLine("lambda表达式中调用局部变量:{0}", pl); 48 49 //调用局部变量的特殊情况 - 匿名函数 50 for (int i = 0; i < 3; i++) 51 { 52 int j = i * 2 + 1; 53 delTestNoParam += delegate() 54 { 55 Console.Write("匿名函数-变量定义于循环内,输出:"); 56 Console.Write(j + ", "); 57 Console.WriteLine(); 58 }; 59 } 60 61 int k; 62 for (int i = 0; i < 3; i++) 63 { 64 k = i * 2 + 1; 65 delTestNoParam += delegate() 66 { 67 Console.Write("匿名函数-变量定义于循环外,输出:"); 68 Console.Write(k + ", "); 69 Console.WriteLine(); 70 }; 71 } 72 73 //调用局部变量的特殊情况 - lambda表达式 74 for (int i = 0; i < 3; i++) 75 { 76 int j = i * 2 + 1; 77 delTestNoParam += () => 78 { 79 Console.Write("lambda表达式-变量定义于循环内,输出:"); 80 Console.Write(j + ", "); 81 Console.WriteLine(); 82 }; 83 } 84 85 int m; 86 for (int i = 0; i < 3; i++) 87 { 88 m = i * 2 + 1; 89 delTestNoParam += () => 90 { 91 Console.Write("lambda表达式-变量定义于循环外,输出:"); 92 Console.Write(m + ", "); 93 Console.WriteLine(); 94 }; 95 } 96 } 97 98 public void ShowVarible() 99 { 100 delTestWithParam += delegate(int int_p, string str_p) 101 { 102 Console.WriteLine("del - 匹配参数int:{0},string:{1}的委托的方法被在匿名函数中调用", int_p, str_p); 103 }; 104 105 //lambda表达式 106 delTestWithParam += (i, s) => Console.WriteLine("evt - 匹配参数int:{0},string:{1}的委托的方法被在lambda表达式中调用", i, s); 107 } 108 }
下面引用MSDN对匿名函数/Lambda表达式变量范围问题的总结:
下列规则适用于 Lambda 表达式中的变量范围:
捕获的变量将不会被作为垃圾回收,直至引用变量的委托超出范围为止。 在外部方法中看不到 Lambda 表达式内引入的变量。 Lambda 表达式无法从封闭方法中直接捕获 ref 或 out 参数。 Lambda 表达式中的返回语句不会导致封闭方法返回。 Lambda 表达式不能包含其目标位于所包含匿名函数主体外部或内部的 goto 语句、break 语句或 continue 语句。