前言

本文已更新至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. 分享一个linux环境下快速读取行数的命令

    最初是因为我需要计算一天的日志行数,如果用传统意义上的cat  a.log |wc -l的话因为是单线程,所以需要计算半小时的样子,后来同组的小伙伴教了我一个方法可以有效提高计算速度,将计算时间减半. ...

  2. Unity3d内置浏览器

    uWebKit是一个Unity3d插件,个人认为比较强大,值得收藏啊 有图有真相: 安装以及破解说明: 1.导入资源包 2.将破解目录里的Editor复制到工程项目的Assets目录下进行覆盖 3.打 ...

  3. sql server 2005 32位+64位、企业版+标准版、CD+DVD 下载地址大全 .

    企业版DVD SQL Server 2005 Enterprise Edition(支持超大型企业) 32 位DVD: ed2k://|file|cs_sql_2005_ent_x86_dvd.iso ...

  4. 如何用js实现截取一个字符串中的数字

    比如var v ="我要提问1098";var v="我0要提问"var v="我还是要提问987"等我想要里边的 1098 ,0, 987 ...

  5. dotnet use regex two samples

    One sample is used to replace double quote from words which encapsulated by csvwriter , you know csv ...

  6. 使用selenium实现右键另存为保存文件

    1.需要借住autoit工具和Robot类,下载地址:https://www.autoitscript.com/site/autoit/downloads/ 2.autoit的使用不再详细讲解.如下图 ...

  7. 最小生成树 2429: [HAOI2006]聪明的猴子

    BZOJ 2429: [HAOI2006]聪明的猴子 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 877  Solved: 566[Submit][ ...

  8. $.ajax()方法详解及实例

    1.url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type: 要求为String类型的参数,请求方式(post或get)默认为get.注意其他http请求方法,例如 ...

  9. Topcoder SRM 619 DIv2 500 --又是耻辱的一题

    这题明明是一个简单的类似约瑟夫环的问题,但是由于细节问题迟迟不能得到正确结果,结果比赛完几分钟才改对..耻辱. 代码: #include <iostream> #include <c ...

  10. RabbitMQ 一二事 - 简单队列使用

    消息队列目前流行的有三种 1. RabbitMQ 2. ActiveMQ 3. Kafka 这三种都非常强大,RabbitMQ目前用的比较多,也比较流行,阿里也在用 ActiveMQ是阿帕奇出品,但是 ...