c#之Lambda表达式
Lambda表达式
前言:我们可以使用 一个新语法把实现的代码赋予委托:Lambda表达式。只要有委托参数类型的地方就可以使用Lambda表达式。我们把我们上一篇博客中的例子改为Lambda表达式。
完整的代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace _15匿名方法 { class Program { static void Main(string[] args) { string mid = ",我是中间的,"; Func<string, string> lambda = param => { param += mid; param += "我是最后的"; return param; }; Console.WriteLine(lambda("我是最前面的")); Console.ReadKey(); } } }
代码的显示如下:
Lambda表达式有几种定义参数的方式。“=>”的左边列出了需要的参数,Lambda运算符的右边定义了赋予Lambda变量的方法的实现的代码。
一:参数:
Lambda表达式有几种定义参数的方式,如果只有一个参数的话,只写出参数名就可以了。下面的Lambda表达式使用了参数s。因为委托定义了一个string参数,所以s的类型就是string类型。实现代码调用String.Format();方法来返回一个字符串,在调用该委托的时候,就把字符串写在控制台上。
Func<string, string> oneParam = s => string.Format("转换为大写的形式 :{0}", s.ToUpper()); Console.WriteLine(oneParam("hao"));
截图如下:
如果使用多个参数的时候,就把参数名放在花括号中,这里参数x和y都是double类型,由Func<double,double,double>委托定义:
private static Func<double, double, double> _twoParams = (x, y) => x * y;
方法的调用:
Console.WriteLine(_twoParams(3, 2));
我们可以不在花括号中添加参数的类型,因为编译器会自动的帮助我们匹配参数的类型。
二:多行代码:
如果Lambda表达式中只有一条语句的话,在方法块内就不需要花括号和return语句,因为编译器会添加一条隐式的return语句。比如我们看下面的一行代码:
private static Func<double, double> squate = x => x * x;
但是添加或括号和return语句也是完全合法的,但是添加这些符号不容易阅读,添加之后如下:
private static Func<double, double> squate = x => { return x * x; };
但是在Lambda表达式中如果想实现多条语句的时候,就必须加上花括号和return;
Func<string, string> anonDel = param => { param += mid; param += "and this was added to the string"; return param; };
三:闭包:
通过Lambda表达式可以访问Lambda表达式外部的变量。这称为闭包. 闭包是一个非常好的功能,但是如果使用不当的话,也会非常的危险,我们首先看一个例子:
int someVal = 5; Func<int, int> f = x => x + someVal;
假设不假设在调用f时,Lambda表达式创建了一个以后使用的新方法,这似乎没有什么问题,我们看看我们上面写的代码,调用f的返回值应该是x+5的结果。但似乎不是这样,假定我们要修改someVal,于是调用Lambda表达式时,会使用someVal的新值。调用f(3)的结果是10;
特别是,当通过另一个线程调用Lambda表达式的时候,我们可能不知道进行了这个调用,也不知道外部的变量的当前值是什么?那我们Lambda表达式在访问外部的变量的时候,对于Lambda :x=>x + someVal ,编译器会创建一个匿名类,它有一个构造函数来传递外部的变量。该构造函数取决于从外部传递进来的变量个数。对于这个简单的例子。构造函数会接收一个int。匿名类包含一个匿名的方法。完整的代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace _16Lambda表达式 { /// <summary> /// 匿名类 /// </summary> public class AnonymousClass { private int _someVal; public int SomeVal { get { return _someVal; } set { _someVal = value; } } /// <summary> /// 构造函数初始化赋值操作 /// </summary> /// <param name="someVal">外部传递的值</param> public AnonymousClass(int someVal) { this.SomeVal = someVal; } /// <summary> /// 匿名方法 /// </summary> /// <param name="x"></param> /// <returns></returns> public int AnonymousMathod(int x) { return x + _someVal; } } }
使用Lambda表达式并调用该方法,会创建一个匿名类的实例,并传递调用该方法时的变量。
四:使用foreach语句的闭包:
我们先看代码:
using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; namespace _16Lambda表达式 { class Program { static void Main(string[] args) { //泛型集合 List<int> values = new List<int>() { 10, 20, 30 }; //泛型集合中存储一个委托类型的方法(有返回值无参数) List<Func<int>> funcs = new List<Func<int>>(); //遍历values存储到集合中 //在集合中添加的对象是没有参数的但是返回values集合中的每一个值 foreach (var val in values) { funcs.Add(() => val); } //遍历集合删除所有的方法 f():无参数有一个int类型的值 foreach (var f in funcs) { Console.WriteLine(f()); } Console.ReadKey(); } } }
在上面的例子中,首先用10 20 30 填充了一个名为values的列表,变量funcs引用了一个泛型列表,其中的每一个对象都引用Func<int>类型的委托。第一条foreach语句添加了funcs列表中的每一个元素。添加到项中的函数使用Lambda表达式定义。该Lambda表达式使用了一个变量val,该变量在Lambda表达式的外部定义为foreach语句的循环变量。第二条foreach语句迭代func列表,已调用列表中引用的每个函数。结果如下:
在c#5.0中,这段代码发生了变化,但是使用c#4或者是更早的版本的时候,会在控制台中输出3次30,在第一个foreach循环中国使用闭包时,所创建的函数是在调用,而不是在迭代是获得val的值。编译器会从foreach语中创建一个while循环。在c#4中,编译器在while循环外部定义循环变量。在每次迭代是重用这个变量。因此,在循环结束的时候,该变量的值就是最后一次迭代的值。要想在使用c#4时,让代码的结果为10 20 30,必须将代码改为使用一个局部的变量。并将这个局部的变量传入Lambda表达式。这样的话,每次迭代的值将保留一个不同的值。完整的代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; namespace _16Lambda表达式 { class Program { static void Main(string[] args) { //泛型集合 List<int> values = new List<int>() { 10, 20, 30 }; //泛型集合中存储一个委托类型的方法(有返回值无参数) List<Func<int>> funcs = new List<Func<int>>(); //遍历values存储到集合中 //在集合中添加的对象是没有参数的但是返回values集合中的每一个值 foreach (var val in values) { var v = val; funcs.Add(() => v); } //遍历集合删除所有的方法 f():无参数有一个int类型 foreach (var f in funcs) { Console.WriteLine(f()); } Console.ReadKey(); } } }
在c#5.0中,不在需要做这种代码的修改了(即将代码修改为局部的变量)。在c#5.0会在while循环的代码块中创建一个不同的局部循环变量。所以值会自动的得到保存。这是c#4.0和c#5.0的区别,是我们必须要知道的。