前言

本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要学习记录以下内容:

  建议29、区别LINQ查询中的IEnumerable<T>和IQueryable<T>

  建议30、使用LINQ取代集合中的比较器和迭代器

  建议31、在LINQ查询中避免不必要的迭代

建议29、区别LINQ查询中的IEnumerable<T>和IQueryable<T>

  LINQ查询方法一共提供了两类扩展方法,在System.Linq命名空间下,有两个静态类:

    Enumerable类,它针对继承了IEnumerable<T>接口的集合类进行扩展。

    Queryable类,它针对继承了IQueryable<T>接口的集合类进行扩展。

稍加观察我们会发现,接口IQueryable<T>实际也是继承了IEnumerable<T>接口的,所以致使这两个接口额方法在很大成都上是一致的。简单的来表述就是:本地数据源用IEnumerable<T>,远程数据源用IQueryable<T>。

  LINQ查询从功能上来讲实际上可以分为三类:LINQ to OBJECTS、LINQ to  SQL、LINQ to XML。设计Enumerable<T>和Queryable<T>两套接口的原因是为了区别对待LINQ to OBJECTS、LINQ to SQL,两者对于查询的处理在内部使用的是完全不同的机制。针对LINQ to OBJECTS时,使用Enumerable中的扩展方法对本地集合进行排序和查询等操作,查询参数接受的是Func<>。Func<>叫做谓语表达式,相当于一个委托。针对LINQ to SQL时,则使用Queryable中的扩展方法,它接受的参数是Expression<>。Expression<>用于包装Func<>。LINQ to SQL引擎最终会将表达式树转化成为相应的SQL语句,然后在数据库中执行。

  那么到底什么时候使用IQueryable<T>,什么时候使用IEnumerable<T>呢?我们来简单的看一个例子:

     [Table(Name = "Employees")]
public class Employees
{
[Column(IsPrimaryKey = true,Name="EmployeeID")]
public int Id { get; set; } [Column]
public string FirstName { get; set; } [Column]
public string LastName { get; set; } [Column]
public string Title { get; set; }
}
}
            DataContext dataContext = new DataContext(ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString);
Table<Employees> employees = dataContext.GetTable<Employees>();
var temp1 = (from p in employees where p.Title.StartsWith("S") select p).AsEnumerable<Employees>();
var temp2 = from p in temp1 where p.FirstName.ToUpper().IndexOf("A") > select p;
foreach (var item in temp2)
{
Console.WriteLine(string.Format("FirstName:{0}\tLastName:{1}\t Title:{2}",item.FirstName,item.LastName,item.Title));
}
Console.ReadLine();

通过上面的代码可以发现,虽然我们针对temp1使用的是延迟求值,但是在整个LINQ查询语句的最后对结果使用了AsEnumerable方法,这相当于将远程数组转成了本地数据。通过数据库的见识工具也可以验证这一点。

现在来看另外一个查询,其实还是上面的查询只是做了简单的修改

            DataContext dataContext = new DataContext(ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString);
Table<Employees> employees = dataContext.GetTable<Employees>();
var temp1 = from p in employees where p.Title.StartsWith("S") select p;
var temp2 = from p in temp1 where p.FirstName.ToUpper().IndexOf("A") > select p;
foreach (var item in temp2)
{
Console.WriteLine(string.Format("FirstName:{0}\tLastName:{1}\t Title:{2}",item.FirstName,item.LastName,item.Title));
}
Console.ReadLine();

通过监控可以发现它是组合两个查询语句,而生成了一条SQL,如果不理解这一点,那么在编写程序时将会造成性能损耗。在LINQ to SQL的查询中,要尽量始终使用IQueryable<T>。

在使用IQueryable<T>和IEnumerable<T>的时候还需要注意一点,IEnumerable<T>查询的逻辑可以直接用我们自己所定义的方法,IQueryable<T>则不能使用自定义的方法,它必须先生成表达式树,查询由LINQ to SQL引擎处理。在使用IQueryable<T>查询的时候,如果使用自定义的方法,则会抛出异常。

建议30、在查询中使用Lambda表达式

http://www.cnblogs.com/aehyok/p/3631483.html可以查看之前写过的一篇文章中的建议10,来回顾一下比较器。

可以发现以上方式实现的排序至少存在两个问题:

1)可扩展性太低,如果存在新的排序要求,就必须实现新的比较器。

2)对代码的侵入性太高,为类型继承了接口,增加了新的 方法。

那么有没有一种方法,即使类型只存在自动实现的属性,也能满足多方面的排序要求呢?答案是使用LINQ。LINQ提供了类似于SQL的语法来实现遍历、筛选与投影集合的功能。借助于LINQ的强大功能。

来看使用LINQ之后的代码:

    public class Salary
{
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; } /// <summary>
/// 基本工资
/// </summary>
public int BaseSalary { get; set; } /// <summary>
/// 奖金
/// </summary>
public int Bouns { get; set; }
}
static void Main(string[] args)
{
List<Salary> array = new List<Salary>();
array.Add(new Salary() { Name = "aehyok", BaseSalary = , Bouns = });
array.Add(new Salary() { Name = "Kris", BaseSalary = , Bouns = });
array.Add(new Salary() { Name = "Leo", BaseSalary = , Bouns = });
array.Add(new Salary() { Name = "Niki", BaseSalary = , Bouns = }); Console.WriteLine("根据BaseSalary排序:");
var list=from p
in array
orderby p.BaseSalary
select p;
foreach (Salary item in list)
{
Console.WriteLine("Name={0},\tBaseSalary={1},\tBouns={2}",item.Name,item.BaseSalary,item.Bouns);
} Console.WriteLine("根据Bouns排序");
var listBouns=from p
in array
orderby p.Bouns
select p;
foreach (Salary item in listBouns)
{
Console.WriteLine("Name={0},\tBaseSalary={1},\tBouns={2}", item.Name, item.BaseSalary, item.Bouns);
}
Console.ReadLine();
}

执行结果如下:

我们可以利用LINQ强大的功能来简化自己的编码,但是LINQ功能的实现本身就是借助于FCL泛型集合的比较器、迭代器、索引器的。LINQ相当于封装了这些功能,让我们使用起来更加的方便。在命名空间System.Linq下存在很多静态类,这些静态类存在的意义就是FCL的泛型集合提供扩展方法。

强烈建议你利用LINQ所带来的便捷性,但我们仍需要掌握比较器、迭代器、索引器的原理,以便更好地理解LINQ的思想,写出更高执行的代码。

建议31、在LINQ查询中避免不必要的迭代

无论是SQL查询还是LINQ查询,搜索到结果立刻返回总比搜索完所有的结果再将结果返回的效率要高。现在简单来创建一个自定义的集合类型来说明。

    public class Person
{
public string Name { get; set; } public int Age { get; set; }
} public class MyList : IEnumerable<Person>
{
List<Person> list = new List<Person>()
{
new Person(){ Name="aehyok",Age=},
new Person(){ Name="Kris",Age=},
new Person(){ Name="Leo",Age=},
new Person(){ Name="Niki",Age=}
}; public int IteratedNum { get; set; } public Person this[int i]
{
get { return list[i]; }
set { this.list[i] = value; }
} public IEnumerator<Person> GetEnumerator()
{
foreach (var item in list)
{
IteratedNum++;
yield return item;
}
} IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

简单的进行调用

            MyList list = new MyList();

            var temp = (from c in list where c.Age ==  select c).ToList();
Console.WriteLine(list.IteratedNum.ToString());
list.IteratedNum = ; var temp2 = (from c in list where c.Age >= select c).First();
Console.WriteLine(list.IteratedNum.ToString()); Console.ReadLine();

通过结果发现,第二种的性能明显比第一种好很多。第一种查询迭代了4次,而第二种仅有1次。

第二种查询仅仅迭代1次是因为25正好放在list的首位,而查询条件是大于等于20.First方法实际完成的工作就是:搜索到满足条件的第一个元素,就从集合中返回。如果没有符合条件的元素,它也会遍历整个集合。

与First方法类似的还有Take方法,Take方法接收一个整型参数,然后为我们返回该参数指定的元素个数。与First一样,它满足条件以后,会从当前的迭代过程直接返回,而不是等到整个迭代过程完毕再返回。如果一个集合包含了很多的元素,那么这种查询会为我们带来可观的时间效率。

再来看下面的例子,虽然LINQ查询的最后结果都是返回包含了两个元素"Niki"对象,但是实际上,使用Take方法仅仅为我们迭代了2次,而使用where查询方式带来的确实整个集合的迭代,首先修改一下集合类中的元素

        List<Person> list = new List<Person>()
{
new Person(){ Name="Niki",Age=},
new Person(){ Name="Niki",Age=},
new Person(){ Name="Kris",Age=},
new Person(){ Name="Leo",Age=},
new Person(){ Name="aehyok",Age=}
};

调用

            MyList list = new MyList();

            var temp = (from c in list select c).Take().ToList();
Console.WriteLine(list.IteratedNum.ToString());
list.IteratedNum = ; var temp2 = (from c in list where c.Name == "Niki" select c).ToList();
Console.WriteLine(list.IteratedNum.ToString()); Console.ReadLine();

结果

在实际的编码过程中,要充分运用First和Take等方法,这样才能为我们的应用带来高效性,而不会让时间浪费在一些无效的迭代中。

英语小贴士

1、Where can I get my baggage?——我在那里可以取得我的行李?

2、I can'find my baggage.——我找不到我的行李。

3、Please wait for a moment while we are investigating.——我们正在调查,请稍等一下。

4、Here is my claim tag.——这是我的行李票。

5、We may have lost some baggage so we'd like to make a lost baggage report.

  Would you come with me to the office?——我们可能遗失了几件行李,所以必须填份行李遗失报告。请和我到办公室?

6、Could you please check it urgently?——是否可麻烦紧急查询?

7、How soon will I find out?——多快可找到?

作者:aehyok

出处:http://www.cnblogs.com/aehyok/

感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,那不妨点个推荐吧,谢谢支持:-O。

编写高质量代码改善C#程序的157个建议[IEnumerable<T>和IQueryable<T>、LINQ避免迭代、LINQ替代迭代]的更多相关文章

  1. 编写高质量代码改善C#程序的157个建议[1-3]

    原文:编写高质量代码改善C#程序的157个建议[1-3] 前言 本文主要来学习记录前三个建议. 建议1.正确操作字符串 建议2.使用默认转型方法 建议3.区别对待强制转换与as和is 其中有很多需要理 ...

  2. 读书--编写高质量代码 改善C#程序的157个建议

    最近读了陆敏技写的一本书<<编写高质量代码  改善C#程序的157个建议>>书写的很好.我还看了他的博客http://www.cnblogs.com/luminji . 前面部 ...

  3. 编写高质量代码改善C#程序的157个建议——建议157:从写第一个界面开始,就进行自动化测试

    建议157:从写第一个界面开始,就进行自动化测试 如果说单元测试是白盒测试,那么自动化测试就是黑盒测试.黑盒测试要求捕捉界面上的控件句柄,并对其进行编码,以达到模拟人工操作的目的.具体的自动化测试请学 ...

  4. 编写高质量代码改善C#程序的157个建议——建议156:利用特性为应用程序提供多个版本

    建议156:利用特性为应用程序提供多个版本 基于如下理由,需要为应用程序提供多个版本: 应用程序有体验版和完整功能版. 应用程序在迭代过程中需要屏蔽一些不成熟的功能. 假设我们的应用程序共有两类功能: ...

  5. 编写高质量代码改善C#程序的157个建议——建议155:随生产代码一起提交单元测试代码

    建议155:随生产代码一起提交单元测试代码 首先提出一个问题:我们害怕修改代码吗?是否曾经无数次面对乱糟糟的代码,下决心进行重构,然后在一个月后的某个周一,却收到来自测试版的报告:新的版本,没有之前的 ...

  6. 编写高质量代码改善C#程序的157个建议——建议154:不要过度设计,在敏捷中体会重构的乐趣

    建议154:不要过度设计,在敏捷中体会重构的乐趣 有时候,我们不得不随时更改软件的设计: 如果项目是针对某个大型机构的,不同级别的软件使用者,会提出不同的需求,或者随着关键岗位人员的更替,需求也会随个 ...

  7. 编写高质量代码改善C#程序的157个建议——建议153:若抛出异常,则必须要注释

    建议153:若抛出异常,则必须要注释 有一种必须加注释的场景,即使异常.如果API抛出异常,则必须给出注释.调用者必须通过注释才能知道如何处理那些专有的异常.通常,即便良好的命名也不可能告诉我们方法会 ...

  8. 编写高质量代码改善C#程序的157个建议——建议152:最少,甚至是不要注释

    建议152:最少,甚至是不要注释 以往,我们在代码中不写上几行注释,就会被认为是钟不负责任的态度.现在,这种观点正在改变.试想,如果我们所有的命名全部采用有意义的单词或词组,注释还有多少存在的价值. ...

  9. 编写高质量代码改善C#程序的157个建议——建议151:使用事件访问器替换公开的事件成员变量

    建议151:使用事件访问器替换公开的事件成员变量 事件访问器包含两部分内容:添加访问器和删除访问器.如果涉及公开的事件字段,应该始终使用事件访问器.代码如下所示: class SampleClass ...

随机推荐

  1. 2014年小结之sql语句优化

    之前接手一个数据统计处理的小程序,本来逻辑上并没什么,只是数据量略大,某些表的数据达到了千万级别..因为是统计,所以免不了各种连接各种查询,结果这个小程序写完后运行一次要1个小时..这的确有点出乎意料 ...

  2. WEB安全--CSRF剖析

    CSRF攻击:攻击者构造合法的HTTP请求,随后利用用户的身份操作用户帐户的一种攻击方式. 一.CSRF攻击原理CSRF的攻击建立在浏览器与Web服务器的会话中:欺骗用户访问URL.二.CSRF攻击场 ...

  3. openfire+asmack搭建的安卓即时通讯(四) 15.4.10

    之前的教程不知道你们成功了没,,,没成功可以问我啊=-= 第四篇博文是要实现发送消息的功能. 首先在我们登陆后的活动的layout里添加这样的两个控件,一个EditText和一个Button用于发送数 ...

  4. Laxcus大数据管理系统2.0(5)- 第二章 数据组织

    第二章 数据组织 在数据的组织结构设计上,Laxcus严格遵循数据和数据描述分离的原则,这个理念与关系数据库完全一致.在此基础上,为了保证大规模数据存取和计算的需要,我们设计了大量新的数据处理技术.同 ...

  5. UVA-10269 (floyd+dijkstra)

    题意: 现在有A个村庄,B个城堡,现在要从1到A+B,有M条路,魔法鞋最多能用K次,每次的长度不超过L,且起点和终点一定是村庄和城堡,而且每次使用魔法鞋不能穿过城堡,问最短时间是多少; 思路: 先用F ...

  6. Codeforces Round #257(Div.2) D Jzzhu and Cities --SPFA

    题意:n个城市,中间有m条道路(双向),再给出k条铁路,铁路直接从点1到点v,现在要拆掉一些铁路,在保证不影响每个点的最短距离(距离1)不变的情况下,问最多能删除多少条铁路 分析:先求一次最短路,铁路 ...

  7. 导航(NavanavigationController)push和pop

    //跳转到指定的控制器 for (UIViewController *Vc in self.navigationController.viewControllers) { if ([Vc isKind ...

  8. 用bower命令创建项目

    1,先安装bower,npm install -g bower 2,cd到项目文件夹下,安装项目所需要的依赖包,比如 npm install jquery;npm install bootstrap, ...

  9. [原创]Gerrit中文乱码问题解决方案分享

    应开发同事的要求,部署了Gitlab+Gerrit+Jenkins的持续集成环境. 但是发现了一个问题,Gerrit登陆后有中文乱码出现. 具体情况如下: (1)Git代码中的中文乱码处理: 为妥善解 ...

  10. Jython概要

    1.安装jython 1.1 进入http://www.jython.org/downloads.html ,网页上会显示当前最稳定的版本(The most current stable releas ...