前言

本文已更新至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. Count and Say

    Count and Say https://leetcode.com/problems/count-and-say/ The count-and-say sequence is the sequenc ...

  2. Java基础の第一弹

    一.虚拟机的工作机制 (1) :通过 ClassLoader 寻找和装载 class 文件 (2) :解释字节码成为指令并执行,提供 class 文件的运行环境 (3) :进行运行期间垃圾回收 (4) ...

  3. [转]AngularJs 多语言的使用 angular-translate

    本文转自:http://www.tuicool.com/articles/zeymimB 随着世界各地Web访问量的增加,作为开发者的我们也在不断让应用国际化.本地化.当用户访问我们的应用时,他应该能 ...

  4. 2016开发一个app需要多少钱?app开发需要哪些成本-app开发问题汇总-广州达到信息

    作为一个APP开发从业者,被外行的朋友们问及最多的问题是,"做一个网站需要多少钱?"或者"开发一个APP需要多少钱?".作为开发过完整网站项目和手机APP的人, ...

  5. 分布式服务框架 Zookeeper(转)

    分布式服务框架 Zookeeper -- 管理分布式环境中的数据 Zookeeper 分布式服务框架是 Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题 ...

  6. [3d跑酷] Xcode5 打包 发布配置

    主题 Unity导出Xcode项目,使用Xocde打包ipa并提交到AppStore xcode发布配置 1.设置发布相关参数,比如 包名,版本,证书,ios设备版本 2.设置体系结构,支持的平台(I ...

  7. Java AtomicInteger

    AtomicInteger,一个提供原子操作的Integer的类.在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字.而AtomicIn ...

  8. java 16 -7 泛型方法和泛型接口(泛型类相似)

    写一个ObjectTool类 泛型方法:把泛型定义在方法上 格式 public <泛型类型> 返回类型 方法名(泛型类型) 这样的好处是: 这个泛型方法可以接收任意类型的数据 public ...

  9. java9-6 内部类

    1. 内部类概述: 把类定义在其他类的内部,这个类就被称为内部类. 举例:在类A中定义了一个类B,类B就是内部类. 内部的访问特点: A:内部类可以直接访问外部类的成员,包括私有. B:外部类要访问内 ...

  10. setAttribute改变属性,动态改变类

    <style type="text/css"> .box{color:red;} </style> <div>通过setAttribute添加d ...