linq入门学习 linq to object

在上一篇随笔中,对lambda表达式进行了简单的介绍,对lambda表达式的使用有了初步了解以及如何编写lambda表达式。在这篇随笔中我们开始把

lambda表达式应用到实际程序中。lambda表达式在LINQ中使用很广泛,下面我们看到Lambda表达式在LINQ to objects中的使用,首先我们从linq to objects的基础开始。

 

 

 

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的静态函数的时候将数据集合作为第一个参数传入,

代码示例如下。

 

复制代码
  
  
1 void Main() 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()进行转换为序列。示例如下:

 

复制代码
  
  
1 void Main() 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>被遍历列举的时候才会真正返回查询结果集合。

下面我们通过代码示例来验证:

 

复制代码
  
  
1 void Main() 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. 实际保存的是查询条件和约束,源数据发生变化不会影响查询结果的准确性。

 

复制代码
  
  
1 void Main() 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,在某些时候也会成为我们的错误。因此在使用的时候要很仔细。

        下面我们还是用代码示例来说明这个问题。

复制代码
  
  
1 void Main() 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等几个非延迟

方法可以将查询结果返回。

 

  
  
1 void Main() 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是通过扩展函数提供查询操作功能,这里我们对扩展函数做个简单说明,如果你已经了解了扩展函数,

可以直接跳过.扩展函数允许你直接在已经存在的类型上添加函数,而不用修改原有类型,或者通过继承的方式.扩展函数是一种特殊的静态函数,但是是需要通过类

型的实例来调用,下面是扩展函数的格式:

 

  
  
1 public static IEnumerable < T > Where < T > ( 2 this IEnumerable < T > source, 3 Func < T, int , bool > predicate);

上面是IEnumerable<T>的标准操作符where,下面我们举例实现自己的扩展操作符,扩展string类型增加函数统计指定的字符的数量GetCharCount(),

这里我们需要注意几点:

1.推荐定义自己的域名空间,统一管理扩展函数。

2.扩展函数和所在的类都要是静态的。

3.允许重载现有的函数。

 

复制代码
  
  
1 namespace MyExtension //自定义域名空间 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 }    
复制代码

使用扩展函数的示例如下:

 

 

复制代码
  
  
1 using MyExtension // 引用我们扩展域名空间 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,定义如下:

 

  
  
string [] items = { " charp " , " cpp " , " python ","perl" };

 

1.  Where

作用: 过滤序列,将结果放入新序列中

是否延迟:   Yes

参数重载1:   Func<T,bool> 委托  

参数重载2:   Func<T,int,bool>委托  (int 标识索引index)

 

  
  
var result = items.Where(p => p.Length == 4 ); //描述: 返回字符串长度为4的序列
 
var result=item.Where((p,i)=>i==1); //i为索引 //描述: 返回索引为1的字符串

 

 

2. Select

作用:对序列元素进行操作,返回新的结果序列(返回序列的类型和原序列类型可以不同)

是否延迟:Yes

参数重载1:Func<T, int, S> selector

参数重载2:Func<T, S> selector

 

  
  
var result = items.Select(p=>p.Length ); //描述: 返回序列中所有元素的长度
 
var result=items.Select((p,i)=>p+":"+i)
//描述: 返回序列中所有元素和序号的组合序列

 

3. SelectMany

作用:创建新的一对多关系的序列,将原序列中每个元素进行操作转换为新的序列。

是否延迟:Yes

参数重载1:Func<T, IEnumerable<S>> selector

参数重载2:Func<T, int, IEnumerable<S>> selector

 

复制代码
  
  
var result = items.SelectMany(p=>p.ToArray() ); //描述:将序列中string元素转换为字符数组,即结果是“cpp”被转换为IEnerable<Char> 值为{ ‘c’,‘p’,‘p’}
 
var result=items.SelectMany((p,i)=>i==1?p.ToArray():new char[]{})
//描述: 将序列中序号为1的元素转换为字符数组,其他的元素转换为空字符数组
复制代码

 

4. Take

作用:从原序列中获取指定数量的元素集合,返回新的序列。

是否延迟:Yes

参数:int count

 

  
  
var result = items.Take(1 ); //描述: 返回序列的第一个元素 结果为"Csharp"

 

5. TakeWhile

作用:从原序列中yield满足条件的元素,直到遇到不满足条件的元素,剩余的其他元素将被忽略。

是否延迟:Yes

参数重载1:Func<T, bool> predicate

参数重载2:Func<T, int, bool> predicate

 

复制代码
  
  
string[] s={"1","22","333","4444","555","66","7"};
var result= s.TakeWhile(p=>p.Length<4);
 //描述: 返回满足长度小于4的序列,注意:当遍历到"4444"的时候不满足条件yield结束,后面的元素虽然满足条件但是被忽略
//结果:
1
22
333
复制代码

 

6. Skip

作用:从原序列中跳过指定数量的元素,返回剩余元素组成的序列。

是否延迟:Yes

参数:int count

 

  
  
var result= items.Skip(2);
 //描述: 跳过两个元素返回剩余的元素组成的序列
//结果: { " python ","perl" };

 

7. SkipWhile

作用:从原序列中跳过满足条件的元素,返回剩余元素组成的序列。

是否延迟:Yes

参数:Func<T, int, bool> predicate

 

  
  
var result= items.SkipWhile(p=>p.Contains(‘h’));
 //描述: 跳过元素中有包含字符h的元素,返回其他元素
//结果: { "cpp " ,"perl" };

 

7. OrderBy 和 OrderByDescending

作用:从原序列中跳过满足条件的元素,返回剩余元素组成的序列。

是否延迟:Yes

参数:Func<T, int, bool> predicate

 

 

  
  
var result= items.SkipWhile(p=>p.Contains(‘h’));
 //描述: 跳过元素中有包含字符h的元素,返回其他元素
//结果: { "cpp " ,"perl" };

 

  8.ThenBy和 ThenByDescending

  作用:在已排序后的序列进行第二种方式排序。

是否延迟:Yes

重载参数1:Func<T, K> Selector

重载参数2:Func<T, K> Selector,IComparer<K> comp

 

 

  
  
1 var result = items.OrderBy(s => s.Length).ThenBy(s => s[ 0 ]);   // 描述:先按照长度排序,然后按照字符串首字母排序

 

 

 

9.Join

作用:通过指定的键连接多个序列组合成新序列,和SQL的内连接相似。

是否延迟:Yes

参数:IEnumerable<U> inner,Func<T, K> outerKey,Func<U, K> innerKey,Func<T, U, V> result

 

 

复制代码
  
  
1 void Main() 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>>

 

 

复制代码
  
  
1 void Main() 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

 

 

复制代码
  
  
1 void Main() 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

 

 

复制代码
  
  
1 void Main() 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

 

 

复制代码
  
  
1 void Main() 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等从字面意思都可以看出来就没有做描述。

由于理解不够深入,存在的问题请指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值