概述
1. 前言
2. Lambda表达式概念
3. Lambda表达式树的概念和示例
4. Lambda表达式的简单应用
5. 总结
前言
在LINQ刚发布的时候,一直也没有时间去研究下LINQ,特别是在当时各种LINQ to ***纷纷出现,看得眼花缭乱。
在LINQ中增加的lambda表达式一直觉得挺神秘的,看到各位高手把lambda表达式运用得如火纯清,很是羡慕. 最近抽
空学习了解了下LINQ, 对学习的过程进行记录。文章基础,高手请飘过 :-)
计划学习的主要内容是lambda表达式,LINQ to Objects, LINQ to XML. 三个部分。
学习之前,推荐一款帮助学习LINQ的优秀工具LINQPad,下载地址:LINQPad下载 。
【另注:学习过程未免出现差错,欢迎指正】
Lambda表达式的概念
什么是lambda表达式?Lambda 表达式是一种匿名函数,它可以包含表达式和语句,并且可用于创建委托或
表达式目录树类型。我们使用lambda表达式可以帮助我们编写精简和紧凑的代码,许多操作中允许自定义排序和过
滤的函数,在.NET2.0的时候通常使用委托函数来实现,在.NET3.5可以使用lambda表达式。
现在举例说明lambda表达式: Func<int,int> addOne= item=> item+1 ,其中操作符 “=>”读作“Goes to”,
可以理解为操作符左边的是函数的参数,操作符右边是函数体内容。上面我们定义的lambda表达式等同于函数如下:
2 {
3 return item + 1 ;
4 }
那么什么样的表达式才是合法的lambda表达式呢?
1. lambda表达式可以是多个参数。 如: (item1,item2)=>item1+item2;
2. lambda表达式可以是0个参数。 如: ()=>"csharp";
3. lambda表达式可以显示指定参数类型。 (int item1,string item2)=>item1+item2;
4. lambda表达式函数体可以使用多条语句. (item1)=>{string ret="hello"+item1;return ret;};
使用lambda表达式的时候,不得不提到泛型委托。在上面我们定义的表达式如:(item1,item2)=>item1+item2;
只是定义的表达式,我们如何调用呢?我们可以定义自己的函数委托来引用lambda表达式,如下
2 void Main()
3 {
4 addOneDelegate fun = (item1,item2) => item1 + item2;
5 var result = fun( 123 , 456 );
6 result.Dump( " 结果 " );
7 }
8
9
10 .结果
11 579
在这里我们可以使用.NET类库中已经提供的泛型委托Func<T>和Action<T>来引用lambda表达式.代码如下
2 {
3 Func < int , int > fun = (item1,item2) => item1 + item2;
4 var result = fun( 123 , 456 );
5 result.Dump( " 结果 " );
6 }
7
8 .结果
9 579
关于Func<T>是泛型委托,最后的一个类型是指返回结果的类型,前面都是输入参数类型,上面的例子中,我们
的输入类型是INT,返回类型也是INT。同样如果我们定义Func<int,string,bool>,是指输入参数有两个,一个是int
类型,一个是string类型,函数返回是bool类型。使用泛型委托可以帮助我们方便引用lambda表达式。Func<T>提供
了多个重载,如下
2 public delegate T Func < A0, T > (A0 arg0);
3 public delegate T Func < A0, A1, T > (A0 arg0, A1 arg1);
4 public delegate T Func < A0, A1, A2, T > (A0 arg0, A1 arg1, A2 arg2);
5 public delegate T Func < A0, A1, A2, A3, T > (A0 arg0, A1 arg1, A2 arg2, A3 arg3);
在这里,需要提到一些关于lambda表达式的特性和规则。
1. lambda表达式的引用变量必须是显式类型。编译器对lambda表达式的类型推断是通过返回的引用变量的类型指定。
如下面的语句是非法的。
2 {
3 var c = n => n + 1 ; // Error,Cannot assign lambda expression to an implicitly-typed local variable
4 Func < string > cc => n + 1 ; // Ok
5 }
2. 在lambda表达式中可以直接访问本地变量和全局变量。
2 void Main()
3 {
4 string localVar = " local string " ;
5 Func < string , string > fun = n => n + " can access " + grobalVar + " and " + localVar;
6 fun( " lambda " ).Dump();
7 }
8
9
10 结果:
11 lambda can access grobal string and local string
12
3. lambda表达式的参数可以是ref或out方式传入,在通过ref或out方式传入的时候必须指定参数的具体类型。
2 void Main()
3 {
4 int x = 10 ;
5 RefParameterFunction fun = ( ref int n) => n ++ ;
6 fun( ref x);
7 x.Dump();
8 }
9
10
11 结果
12 11
4. lambda表达式的参数可以支持不定参数数传入。
2 void Main()
3 {
4 int [] x = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 };
5 AddFunction fun = (items) =>
6 {
7 int count = 0 ;
8 foreach ( int item in items)
9 {count = count + item;}
10 return count;
11 };
12 fun(x).Dump( " 求和: " );
13 }
14
15
16 求和:
17 45
Lambda表达式树的概念和示例
Lambda另一个强大的特性就是表达式树,lambda表达式都可以通过表达式树来描述,就不用在代码
中直接编写表达式。这样的优势就是表达式可以在运行的时候编译运行,而且可以对lambda表达式进行动态修改。
要使用lambda表达式树,首先提到一个表达式的泛型类Expression<T>,(域名空间System.Linq.Expressions),
这个类是保存表达式的结构信息。我们把Expession看作一棵树结构,每个结点都是由两部分组成,左树和右树,一直这样
递归下去。这里需要说明一下,刚开始使用表达式树的时候容易和表达式产生混淆,比如:
2 {
3
4 Expression < Func < int , int >> tree = x => x + 1 ;
5 Func < int , int > exp = x => x + 1 ;
6
7 tree( 1 ); // 'tree' is a 'variable' but is used like a 'method'
8 exp( 1 ); // 输出2
9 }
10
11
注意:tree只是lambada表达式的树形结构信息,并不是函数可以直接调用。
现在我们对lambda表达式的树结构输出来查看下,举例:我们编写一个验证三角形是否直角三角形,通过沟谷定律,
我们很容易编写lambda表达式为:
现在我们使用LINQPad的Dump()函数进行输出显示:
2 {
3 Expression < Func < int , int , int , bool >> tree = (x,y,z) => (x * x + y * y) == z * z;
4 tree.Dump();
5 }
输出结果如下:
通过输出的图形,我们可以清楚的看出整个lambda表达式是由LEFT和RIGHT两部分组成的,Left部分和right部分之间
的关系通过 NodeType属性指定,所有的NodeType类型通过枚举(System.Linq.Expressions.ExpressionType )定义,
而结点的Type可以看作返回类型,比如我们定义的 tree的Type是Func<int,int,int,bool>,而Type是Lambda。
那么如何把表达式树转换为可以直接使用的函数呢?Expression类提供了函数Compile(),就可以把我们定义的lambda
表达式树编译为实际的函数,代码如下:
2 {
3 Expression < Func < int , int , int , bool >> tree = (x,y,z) => (x * x + y * y) == z * z;
4 Func < int , int , int , bool > fun = tree.Compile();
5 fun( 3 , 4 , 5 ).Dump();
6 }
7
9 结果
10 True
我们了解到了lambda表达式树的基本概念,现在我们自行构造一个lambda表达式树。还是以上面的验证是否是直角
三角形为例,我们通过System.Linq.Expressions提供了表达式类来构造这个表达式,不参考LINQPad输出的结构。现在
我们分析表达式的树结构,(x,y,z) => (x*x + y*y)== z*z 按照操作符把表达式分为left tree和right tree。比如首先
我们把整个表达式分为左树:x*x + y*y, 右树:z*z, 关系:Equal,以此画出阿里如下:
我们已经把表达式树分析出来,现在我们开始使用.NET提供的表达式类来构造这棵表达式树,在这棵树比较简单,
我们比较用到的类包括二元表达式类(BinaryExpression)和参数表达式类(ParameterExpression)。现在我们
从树的叶结点开始构造。
首先我们需要制定表达式中参数和参数的类型。
2 ParameterExpression expY = Expression.Parameter( typeof ( int ), " y " );
3 ParameterExpression expZ = Expression.Parameter( typeof ( int ), " z " );
接着我们使用二元表达式将参数表达式关联起来,X和X,Y和Y,Z和Z,二元关系都是乘.
2 BinaryExpression mulY = Expression.Multiply(expY, expY);
3 BinaryExpression mulZ = Expression.Multiply(expZ, expZ);
然后我们将X*X+Y*Y通过 加二元表达式关联起来.
最后我们将X*X+Y*Y 和Z*Z通过 等于二元表达式关联起来.
现在我们构造完成后,可以通过编译来执行,下面是完整的代码:
2 {
3 ParameterExpression expX = Expression.Parameter( typeof ( int ), " x " );
4 ParameterExpression expY = Expression.Parameter( typeof ( int ), " y " );
5 ParameterExpression expZ = Expression.Parameter( typeof ( int ), " z " );
6 BinaryExpression mulX = Expression.Multiply(expX, expX);
7 BinaryExpression mulY = Expression.Multiply(expY, expY);
8 BinaryExpression mulZ = Expression.Multiply(expZ, expZ);
9 BinaryExpression addXY = Expression.Add(mulX,mulY);
10 BinaryExpression final = Expression.Equal(mulZ, addXY);
11 Expression < Func < int , int , int , bool >> square =
Expression.Lambda < Func < int , int , int , bool >> (final, expX, expY, expZ);
12 Func < int , int , int , bool > xx = square.Compile();
13 xx( 3 , 4 , 5 ).Dump();
14 }
15
16
17 结果:
18 True
19
Lambda表达式的简单应用
1. 对数组的自定义排序。
2 {
3 string [] items = { " csharp " , " cpp " , " python " , " perl " , " java " };
4 List < string > list = items.ToList();
5 list.Sort((x,y) => y.Length - x.Length);
6
7 list.Dump();
8 }
9
10 结果:
11 python
12 csharp
13 perl
14 java
15 cpp
2. 对数组数据进行搜索
2 {
3 string [] items = { " csharp " , " cpp " , " python " , " perl " , " java " };
4 List < string > list = items.ToList();
5 var result = list.FindAll(x => x.Length == 4 );
6
7 result.Dump();
8 }
9
10 结果:
11 perl
12 java
3. 对数组数据进行直接更新
2 {
3 string [] items = { " csharp " , " cpp " , " python " , " perl " , " java " };
4 var result = items.Select(n => n + " : " + n.Length);
5 result.Dump();
6 }
7
8 结果:
9 csharp : 6
10 cpp : 3
11 python : 6
12 perl : 4
13 java : 4
总结
这篇学习记录对Lambda表达式和Lambda表达式树的最基础进行描述,对lambda没有更多深入的研究。比如在表达式树
的的动态修改,更多复杂的lambda表达式,lamdba表达式树对更复杂函数的构造。如果有兴趣,可以继续研究。
另:这是我发布的第一篇随笔,写一篇随笔原来也是挺艰难,水平有限,希望大家指正,谢谢。
1.什么是LINQ to Objects?
Linq to objects是使用LINQ查询内存中的数据集合,数据集合都需要实现了 IEnumerable or IEnumerable<(Of <(T>)>) 接口。比如我们需要对
一个数组进行排序,就可以使用SQL风格的LINQ to Objects进行这个数组进行排序。
在使用LINQ to objects之前,需要对了解几个概念。IEnumerable<T>泛型接口,允许对实现这个接口的数据集合进行枚举集合内的元素,当然也包
括.NET2.0之前的非泛型接口。 序列(Sequences),我们对实现了IEnumerable<T>接口的集合称为序列。标准查询符,是LINQ提供的的查询的符号。
下面是操作符列表:
from, in | 定义LINQ查询表达式结构,从指定数据集合中提取出数据或新的数据集合 |
where | 对从数据集合中的数据进行约束限制 |
select | 从数据集合中选取出数据 |
join, on, equals, into | 通过特定的键对数据集进行联结 |
orderby,ascending,descending | 对提取出来的子集按升序或降序进行排序 |
group, by | 对提取出来的子集按特定的值进行分组 |
大多数的操作符都是通过IEnumerable<T>的扩展函数的方式以及System.Linq.IEnumerable的静态函数提供查询相关功能,但是通过扩展函数的方
式可以方便对序列(Sequences)进行复杂的查询操作,而不需要每次在调用System.Linq.IEnumerable的静态函数的时候将数据集合作为第一个参数传入,
代码示例如下。
2 {
3 string [] items = { " csharp " , " cpp " , " python " , " perl " , " java " };
4 var lens1 = System.Linq.Enumerable.Select(items,n => n.Length); // 静态函数
5 var lens2 = items.Select(items,n => n.Length); // 扩展函数
6 lens1.Dump( " 长度列表1 " );
7 lens2.Dump( " 长度列表2 " );
8 }
在.NET2.0之前有很多遗留的集合类,比如ArrayList,Stack,Hashtable等非泛型的集合类, 由于没有实现IEnumerable<T>接口,因此我们不能直接
使用LINQ对他们进行查询,而需要通过函数Cast() 或者 OfType()进行转换为序列。示例如下:
2 {
3 ArrayList list = new ArrayList(){ " csharp " , " java " , " vb " };
4 // 编译错误: Could not find an implementation of the query pattern for source type
5 // 'System.Collections.ArrayList'. 'Select' not found. Consider explicitly
6 // specifying the type of the range variable 'item'.
7 var error = from item in list
8 select item;
9 // OK
10 var ok = from item in list.Cast < string > ()
11 select item;
12 // OK too
13 IEnumerable<string> ok2 = list.OfType < string > ().Select(n => n);
14 }
2. LINQ的延迟查询
在上一个示例代码中,IEnumerable<string> ok2=list.OfType<string>().Select(n=>n); ok2保存是什么呢?我们常常以为IEnumerable<T>就是
存了查询出来的序列结果,其实不然,select函数并没有把查询出来的结果返回,而只有在IEnumerable<T>被遍历列举的时候才会真正返回查询结果集合。
下面我们通过代码示例来验证:
2 {
3 string [] items = { " csharp " , " vb " , " java " , " cpp " , " python " };
4 IEnumerable < string > result = items.Where(n => n.Length > 4 );
5 // 显示查询结果
6 Console.WriteLine( " --------enumerated----------- " );
7 foreach ( string item in result)
8 {
9 Console.WriteLine(item);
10 }
11 items[ 0 ] = " not exist " ; // 修改数组的内容
12 // 再次显示查询结果
13 Console.WriteLine( " --------enumerated again----------- " );
14 foreach ( string item in result)
15 {
16 Console.WriteLine(item);
17 }
18 }
19
结果:
--------enumerated-----------
csharp
python
--------enumerated again-----------
not exist
python
我们注意示例代码,result两次遍历发生了变化,因为我们直接修改了数组的内容。我们可以得出结论:查询被延迟了,当我们对IEnumerable<T>进行
遍历列举的时候,序列中的元素才会被yield返回。
现在我们来讨论下延迟查询的作用。延迟查询有什么有点和什么缺点呢?
优点:1. 执行过程中减少资源的占用,提高性能。数据只在使用的时候才读取出来,而不用每次查询都要读取出来。
2. 实际保存的是查询条件和约束,源数据发生变化不会影响查询结果的准确性。
2 {
3 int [] list = { 1 , 2 , 3 , 4 , 5 , 6 , 7 };
4 var result = list.Where(n => n > 4 );
5 result.Dump("结果是:5,6,7");
6 list[ 6 ] = 0 ;
7 result.Dump("结果是:5,6");
8 }
在例子中,我们查询条件是大于4的所有数字,但是中途数组中的数据发生变化,原来的数字 7 修改为了 0,因此实际我们的查询结果应该也会变化。
因此延迟查询可以保证我们一直能够得到想要的数据集合。
缺点:数据可能不一致造成异常。由于我们的查询结果是在实际遍历的时候才会读取出来,因此查询条件的异常错误也就只会在实际遍历时候才会产生,前
面我们总结的延时查询的优点2,在某些时候也会成为我们的错误。因此在使用的时候要很仔细。
下面我们还是用代码示例来说明这个问题。
2 {
3 int [] list = { 1 , 2 , 3 , 4 , 5 , 6 , 7 };
4 var result = list.Select(n => 100 / n);
5 result.Dump(); // 正常
6 // ..其他代码
7 list[ 6 ] = 0 ;
8 // ..其他代码
9 result.Dump(); // 除以0异常
10 }
那么我们如何让查询不是延迟的呢?方法很简单,在IEnumerable<T>的扩展函数中,比如ToArray, ToList, ToDictionary, or ToLookup等几个非延迟
方法可以将查询结果返回。
2 {
3 int [] list = { 1 , 2 , 3 , 4 , 5 , 6 , 7 };
4 var result = list.Select(n => 100 / n).ToList();
5 result.Dump(); // result保存查询结果
6 }
在查询操作符之中,有部分操作符并不是延迟的,下面列举出非延迟和延迟的操作符.
(图片来源:《Pro LINQ: Language Integrated Query in C# 2008》):
3.扩展函数
前面我们说到过,IEnumerable<T>和IEnumerable 是通过扩展函数提供查询操作功能,这里我们对扩展函数做个简单说明,如果你已经了解了扩展函数,
可以直接跳过.扩展函数允许你直接在已经存在的类型上添加函数,而不用修改原有类型,或者通过继承的方式.扩展函数是一种特殊的静态函数,但是是需要通过类
型的实例来调用,下面是扩展函数的格式:
2 this IEnumerable < T > source,
3 Func < T, int , bool > predicate);
上面是IEnumerable<T>的标准操作符where,下面我们举例实现自己的扩展操作符,扩展string类型增加函数统计指定的字符的数量GetCharCount(),
这里我们需要注意几点:
1.推荐定义自己的域名空间,统一管理扩展函数。
2.扩展函数和所在的类都要是静态的。
3.允许重载现有的函数。
2 {
3 public static class StringExtension //静态类
4 { //统计字符在字符串串中出现的次数
5 public static int GetCharCount( this string source, char c) //静态函数
6 {
7 return (from item in source where item = c select item).Count();
8 }
9 }
10 }
使用扩展函数的示例如下:
2 namespace ConsoleApplication1
3 {
4 static class Program
5 {
6 static void Main( string [] args)
7 {
8 string demoString = " this is test string " ;
9 int count = demoString.GetCharCount( ' i ' ); // 直接调用扩展函数
10 Console.WriteLine(x);
11 }
12 }
13 }
4.常见查询操作符使用介绍
在经过了上面对linq to objects的了解,在这一节中,我们对常见的操作符进行说明,在这里会根据操作符的是否属于延迟操作符进行分类。
附注:后面的示例中用到的序列都为 items,定义如下:
1. Where
作用: 过滤序列,将结果放入新序列中
是否延迟: Yes
参数重载1: Func<T,bool> 委托
参数重载2: Func<T,int,bool>委托 (int 标识索引index)
//描述: 返回字符串长度为4的序列
//描述: 返回索引为1的字符串
2. Select
作用:对序列元素进行操作,返回新的结果序列(返回序列的类型和原序列类型可以不同)
是否延迟:Yes
参数重载1:Func<T, int, S> selector
参数重载2:Func<T, S> selector
//描述: 返回序列中所有元素的长度
3. SelectMany
作用:创建新的一对多关系的序列,将原序列中每个元素进行操作转换为新的序列。
是否延迟:Yes
参数重载1:Func<T, IEnumerable<S>> selector
参数重载2:Func<T, int, IEnumerable<S>> selector
//描述:将序列中string元素转换为字符数组,即结果是“cpp”被转换为IEnerable<Char> 值为{ ‘c’,‘p’,‘p’}
4. Take
作用:从原序列中获取指定数量的元素集合,返回新的序列。
是否延迟:Yes
参数:int count
//描述: 返回序列的第一个元素 结果为"Csharp"
5. TakeWhile
作用:从原序列中yield满足条件的元素,直到遇到不满足条件的元素,剩余的其他元素将被忽略。
是否延迟:Yes
参数重载1:Func<T, bool> predicate
参数重载2:Func<T, int, bool> predicate
6. Skip
作用:从原序列中跳过指定数量的元素,返回剩余元素组成的序列。
是否延迟:Yes
参数:int count
7. SkipWhile
作用:从原序列中跳过满足条件的元素,返回剩余元素组成的序列。
是否延迟:Yes
参数:Func<T, int, bool> predicate
7. OrderBy 和 OrderByDescending
作用:从原序列中跳过满足条件的元素,返回剩余元素组成的序列。
是否延迟:Yes
参数:Func<T, int, bool> predicate
8. ThenBy 和 ThenByDescending
作用:在已排序后的序列进行第二种方式排序。
是否延迟:Yes
重载参数1:Func<T, K> Selector
重载参数2:Func<T, K> Selector,IComparer<K> comp
// 描述:先按照长度排序,然后按照字符串首字母排序
9.Join
作用:通过指定的键连接多个序列组合成新序列,和SQL的内连接相似。
是否延迟:Yes
参数:IEnumerable<U> inner,Func<T, K> outerKey,Func<U, K> innerKey,Func<T, U, V> result
2 {
3 string [] items1 = { " aa " , " bbb " , " ccc " , " dddd " };
4 string [] items2 = { " eee " , " ff " , " gggg " , " h " , " iii " };
5 var r = items1.Join(items2,i => i.Length,j => j.Length,(i,j) => new {result = string .Format( " {0}:{1} " ,i,j)});
6 r.Dump();
7 }
8 // 将两个序列按照长度连接组成新序列
结果:
result
aa:ff
bbb:eee
bbb:iii
ccc:eee
ccc:iii
dddd:gggg
注意:
1.返回的序列是匿名类型,可以自定义结果序列的列,但是同时需要指定名称(result就是名称)。
2.由于是匿名类型,因此只能使用var关键字
10.GroupBy
作用:对序列按照指定方式进行分组,生产新的分组序列
是否延迟:Yes
参数:Func<T, K> keySelector
返回值:IEnumerable<IGrouping<K, T>>
2 {
3 string [] items = { " 1 " , " 22 " , " 33 " , " 444 " , " 555 " , " 6666 " };
4 var r1 = from item in items
5 group item by item.Length into g
6 select new {g.Key};
7 r1.Dump();
8
9 // 等同于下面
10 var r2 = items.GroupBy(item => item.Length).Select(g => g.Key);
11 r2.Dump();
12 }
结果:
1
2
3
4
11.Union
作用:组合两个序列为一个序列,去除重复的元素
是否延迟:Yes
参数:IEnumerable<T> second
2 {
3 string [] items = { " 1 " , " 22 " , " 6666 " };
4 string [] items2 = { " 1 " , " 22 " , " 33 " ,, " 777 " };
5 items.Union(items2).Dump();
6 }
结果:
1
22
33
6666
777
12.ToArray , ToList
作用:将序列转换为数组 或 列表
是否延迟:No
参数:none
2 {
3 string [] items = { " 1 " , " 22 " , " 33 " , " 444 " , " 555 " , " 6666 " };
4 var r = items.Where(n => n.Length > 2 ).ToArray();
5 var r2 = items.Where(n => n.Length > 2 ).ToList();
6 }
7 // r和r2保存的是实际的数组和列表,而不是查询条件对象
13.ToDictionary
作用:转换序列为泛型字典Dictionary of type <K, T>
是否延迟:No
参数:Func<T, K> keySelector,Func<T, E> elementSelector,IEqualityComparer<K> comparer
2 {
3 string [] items = { " 1 " , " 22 " , " 33 " , " 444 " , " 555 " , " 6666 " };
4 items.ToDictionary(k => k.Length).Dump(); // 错误:重复键
5 items.ToDictionary(k => k,k => k.Length).Dump();
6 }
结果:
key value
1 1
22 2
33 2
444 3
555 3
6666 4
5.后记
在上面的例子中只列出了几个常见,某些比较简单的操作符比如:Min,Max,Count,ElementAt等从字面意思都可以看出来就没有做描述。
由于理解不够深入,存在的问题请指正。
转载于:https://www.cnblogs.com/vebest/archive/2011/08/21/2148656.html
最后
以上就是欣慰猫咪为你收集整理的Linq学习的全部内容,希望文章能够帮你解决Linq学习所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复