在上一篇随笔中,对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,0)">扩展函数
6
?
lens1.Dump(
长度列表1
"
);
7
lens2.Dump(
长度列表2
8
}
在.NET2.0之前有很多遗留的集合类,比如ArrayList,Stack,Hashtable等非泛型的集合类,由于没有实现IEnumerable<T>接口,因此我们不能直接
使用LINQ对他们进行查询,而需要通过函数Cast() 或者 OfType()进行转换为序列。示例如下:
1
2
{
3
ArrayList list
=
new
ArrayList(){
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>被遍历列举的时候才会真正返回查询结果集合。
下面我们通过代码示例来验证:
3
4
IEnumerable
<
string
>
result
=
items.Where(n
=>
n.Length
>
4
);
5
显示查询结果
?
Console.WriteLine(
--------enumerated-----------
7
foreach
(
string
item
in
result)
8
{
9
Console.WriteLine(item);
10
}
11
items[
0
]
=
not exist
"
;
修改数组的内容
12
再次显示查询结果
--------enumerated again-----------
14
15
{
16
Console.WriteLine(item);
17
}
18
}
19
?
结果: --------enumerated----------- csharp python --------enumerated again----------- not exist python ?
我们注意示例代码,result两次遍历发生了变化,因为我们直接修改了数组的内容。我们可以得出结论:查询被延迟了,当我们对IEnumerable<T>进行
遍历列举的时候,序列中的元素才会被yield返回。
现在我们来讨论下延迟查询的作用。延迟查询有什么有点和什么缺点呢?
优点:1. 执行过程中减少资源的占用,提高性能。数据只在使用的时候才读取出来,而不用每次查询都要读取出来。
??????? 2. 实际保存的是查询条件和约束,源数据发生变化不会影响查询结果的准确性。
int
[] list
=
{
1
,128)">2
,128)">3
,128)">4
,128)">5
,128)">6
,128)">7
};
4
var result
=
list.Where(n
=>
n
>
5
result.Dump("结果是:5,6,7");
6
list[
6
]
=
0
;
7
result.Dump("结果是:5,6");
?????? 在例子中,我们查询条件是大于4的所有数字,但是中途数组中的数据发生变化,原来的数字 7 修改为了 0,因此实际我们的查询结果应该也会变化。
因此延迟查询可以保证我们一直能够得到想要的数据集合。
?
缺点:数据可能不一致造成异常。由于我们的查询结果是在实际遍历的时候才会读取出来,因此查询条件的异常错误也就只会在实际遍历时候才会产生,前
??????? 面我们总结的延时查询的优点2,在某些时候也会成为我们的错误。因此在使用的时候要很仔细。
??????? 下面我们还是用代码示例来说明这个问题。
4
var result
=
list.Select(n
=>
100
/
n);
5
result.Dump();
正常
..其他代码
?
list[
8
..其他代码
9
?
result.Dump();
除以0异常
?
}
?
?
?
?
那么我们如何让查询不是延迟的呢?方法很简单,在IEnumerable<T>的扩展函数中,比如ToArray,ToList,ToDictionary,or ToLookup等几个非延迟
方法可以将查询结果返回。
4
var result
=
list.Select(n
=>
100
/
n).ToList();
5
result.Dump();
result保存查询结果
?
}
在查询操作符之中,有部分操作符并不是延迟的,下面列举出非延迟和延迟的操作符.
(图片来源:《Pro?LINQ: Language Integrated Query in C# 2008》):
![](http://img50.lidatong.com.cn//uploads/allimg/c20201215/bd7cf8d2c0f36433ef683fec9ce1ce5b.gif)
3.扩展函数
前面我们说到过,IEnumerable<T>和IEnumerable是通过扩展函数提供查询操作功能,这里我们对扩展函数做个简单说明,如果你已经了解了扩展函数,
可以直接跳过.扩展函数允许你直接在已经存在的类型上添加函数,而不用修改原有类型,或者通过继承的方式.扩展函数是一种特殊的静态函数,但是是需要通过类
型的实例来调用,下面是扩展函数的格式:
public
static
IEnumerable
<
T
>
Where
<
T
>
(
2
this
IEnumerable
<
T
>
source,
3
Func
<
T,255)">int
,255)">bool
>
predicate);
上面是IEnumerable<T>的标准操作符where,下面我们举例实现自己的扩展操作符,扩展string类型增加函数统计指定的字符的数量GetCharCount(),sans-serif; font-size:13px; line-height:19px"> 这里我们需要注意几点:
1.推荐定义自己的域名空间,统一管理扩展函数。
2.扩展函数和所在的类都要是静态的。
3.允许重载现有的函数。
namespace
MyExtension //自定义域名空间
static
class
StringExtension? //静态类
4
{?? //统计字符在字符串串中出现的次数
int
GetCharCount(
this
string
source,255)">char
c) //静态函数
6
{
return
(from item
in
source
where
item
=
c select item).Count();
8
}
9
}
10
}
使用扩展函数的示例如下:
?
using
MyExtension
引用我们扩展域名空间
2
?
namespace
ConsoleApplication1
3
{
class
Program
5
{
6
void
Main(
string
[] args)
7
{
string
demoString
=
this is test string
int
count
=
demoString.GetCharCount(
'
i
'
);
直接调用扩展函数
?
Console.WriteLine(x);
11
}
12
}
13
}
4.常见查询操作符使用介绍
在经过了上面对linq to objects的了解,在这一节中,我们对常见的操作符进行说明,在这里会根据操作符的是否属于延迟操作符进行分类。
附注:后面的示例中用到的序列都为 items,定义如下:
charp
","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,S> selector
参数重载2:Func<T,204); padding-top:5px; padding-right:5px; padding-bottom:5px; padding-left:5px; overflow-x:auto; overflow-y:auto; margin-top:5px; margin-right:0px; margin-bottom:5px; margin-left:0px">
var result
=
items.Select(p=>p.Length
); //描述: 返回序列中所有元素的长度
?
var result=items.Select((p,i)=>p+":"+i)
//描述: 返回序列中所有元素和序号的组合序列
3.?SelectMany
作用:创建新的一对多关系的序列,将原序列中每个元素进行操作转换为新的序列。
![复制代码](http://img50.lidatong.com.cn//uploads/allimg/c20201215/bd7cf8d2c0f36433ef683fec9ce1ce5b.gif)
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
作用:从原序列中获取指定数量的元素集合,返回新的序列。
参数:int count
var result
=
items.Take(1
); //描述: 返回序列的第一个元素 结果为"Csharp"
5.?TakeWhile
作用:从原序列中yield满足条件的元素,直到遇到不满足条件的元素,剩余的其他元素将被忽略。
![复制代码](http://img50.lidatong.com.cn//uploads/allimg/c20201215/bd7cf8d2c0f36433ef683fec9ce1ce5b.gif)
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
作用:从原序列中跳过指定数量的元素,返回剩余元素组成的序列。
var result= items.Skip(2);
?//描述: 跳过两个元素返回剩余的元素组成的序列
//结果: {
7.?SkipWhile
作用:从原序列中跳过满足条件的元素,返回剩余元素组成的序列。
参数:Func<T,204); padding-top:5px; padding-right:5px; padding-bottom:5px; padding-left:5px; overflow-x:auto; overflow-y:auto; margin-top:5px; margin-right:0px; margin-bottom:5px; margin-left:0px">
var result= items.SkipWhile(p=>p.Contains(‘h’));
?//描述: 跳过元素中有包含字符h的元素,返回其他元素
//结果: {
"cpp
,192)">OrderBy 和 OrderByDescending
8.ThenBy和?ThenByDescending
作用:在已排序后的序列进行第二种方式排序。
重载参数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的内连接相似。
参数:IEnumerable<U> inner,Func<T,K> outerKey,Func<U,K> innerKey,U,V> result
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}
6
r.Dump();
7
}
8
?
将两个序列按照长度连接组成新序列
结果: result aa:ff bbb:eee bbb:iii ccc:eee ccc:iii dddd:gggg
?
注意:
1.返回的序列是匿名类型,可以自定义结果序列的列,但是同时需要指定名称(result就是名称)。
2.由于是匿名类型,因此只能使用var关键字
10.GroupBy
作用:对序列按照指定方式进行分组,生产新的分组序列
返回值:IEnumerable<IGrouping<K,T>>
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();
等同于下面
?
var r2
=
items.GroupBy(item
=>
item.Length).Select(g
=>
g.Key);
11
r2.Dump();
12
} 结果: 1 2 3 4
11.Union
作用:组合两个序列为一个序列,去除重复的元素
参数:IEnumerable<T> second
777
5
items.Union(items2).Dump();
6
}
结果: 1 22 33 6666 777
?
12.ToArray , ToList
作用:将序列转换为数组 或 列表
是否延迟:No
参数:none
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>
4
items.ToDictionary(k
=>
k.Length).Dump();
错误:重复键
?
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等从字面意思都可以看出来就没有做描述。
由于理解不够深入,存在的问题请指正。
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|