无论是SQL查询还是LINQ查询,搜索到结果立刻返回总比搜索完所有的结果再将结果返回的效率要高。由于不能修改现有的FCL集合类型的代码来演示,所以本建议创建了一个自定义的集合类型来阐述所要表达的观点。
我们要创建的集合类型继承自IEnumerable<T>接口,同时它实现了一个自身的索引器。而且在索引方法内部维护了一个迭代变量IteratedNum,集合类型只要完成一次迭代中的循环,该迭代变量IteratedNum就会+1。调用者代码通过访问IteratedNum来确定我们的查询方法是否迭代次数最少、最高效。首先给出该集合类型:
class MyList:IEnumerable<Person>
{
//为了演示需要,模拟了一个元素集合
List<Person>list=new List<Person>()
{
new Person(){Name="Mike",Age=20},
new Person(){Name="Mike",Age=30},
new Person(){Name="Rose",Age=25},
new Person(){Name="Steve",Age=30},
new Person(){Name="Jessica",Age=20}
};
///<summary>
///迭代次数属性
///</summary>
public int IteratedNum{get;set;}
public Person this[int i]
{
get{return list[i];}
set{this.list[i]=value;}
}
#region IEnumerable<Person>成员
public IEnumerator<Person>GetEnumerator()
{
foreach(var item in list)
{
//每遍历一个元素就加1
IteratedNum++;
yield return item;
}
}
#endregion
#region IEnumerable成员
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
class Person
{
public string Name{get;set;}
public int Age{get;set;}
}
针对上述集合,现在要返回年龄等于20的第一个元素。下面有两个LINQ查询模式,我们来考虑一下哪一种效率会更高。
第一种:
from c in list where c.Age==20 select c
第二种:
(from c in list where c.Age>=20 select c).First()
通常我们会认为第一种的效率会更高一些,因为它似乎返回的就是等于20的那两个元素,而第二种模式则需要查询所有大于20的元素。实际情况其实并不是这样的。查看下面的测试语句后我们知道,第一种查询集合迭代了5次,而第二种查询集合仅迭代了1次。
MyList list=new MyList();
var temp=(from c in list where c.Age==20 select c).ToList();
Console.WriteLine(list.IteratedNum.ToString());
list.IteratedNum=0;
var temp2=(from c in list where c.Age>=20 select c).First();
Console.WriteLine(list.IteratedNum.ToString());
以上代码输出为:
5
1
注意 第二次查询仅仅迭代1次是因为20正好放在List的首位。First方法实际完成的工作是:搜索到满足条件的第一个元素,就从集合中返回。如果没有符合条件的元素,它也会遍历整个集合。
与First方法类似的还有Take方法,Take方法接收一个整型参数,然后为我们返回该参数指定的元素个数。与First一样,它在满足条件以后,会从当前的迭代过程直接返回,而不是等到整个迭代过程完毕再返回。如果一个集合包含了很多的元素,那么这种查询会为我们带来可观的时间效率。
注意看下面的例子,虽然LINQ查询的最后结果都是返回包含了两个元素的"Mike"对象,但是实际上,使用Take方法仅仅为我们迭代2次,而使用where查询方式带来的却是整个集合的迭代:
MyList list=new MyList();
var temp=(from c in list select c).Take(2).ToList();
Console.WriteLine(list.IteratedNum.ToString());
list.IteratedNum=0;
var temp2=(from c in list where c.Name=="Mike"select c).ToList();
Console.WriteLine(list.IteratedNum.ToString());
输出为:
2
5
在实际的编码过程中,要充分运用First和Take等方法,这样才能为我们的应用带来高效性,而不会让时间浪费在一些无效的迭代中。