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

前言

本文已更新至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") > 0 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") > 0 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 = 12000, Bouns = 500 });
            array.Add(new Salary() { Name = "Kris", BaseSalary = 11200, Bouns = 400 });
            array.Add(new Salary() { Name = "Leo", BaseSalary = 18000, Bouns = 300 });
            array.Add(new Salary() { Name = "Niki", BaseSalary = 20000, Bouns = 700 });

            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=25},
            new Person(){ Name="Kris",Age=20},
            new Person(){ Name="Leo",Age=25},
            new Person(){ Name="Niki",Age=30}
        };

        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 == 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());

            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=25},
            new Person(){ Name="Niki",Age=30},
            new Person(){ Name="Kris",Age=20},
            new Person(){ Name="Leo",Age=25},
            new Person(){ Name="aehyok",Age=30}
        };

调用

            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 == "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。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端侠2.0

c# 的析构以及垃圾回收2、3事!

看书时,自己写的例子代码,了解到几个知识点,记载下来。同时发现自己手写代码的能力比较弱,还是得多写一下。

8410
来自专栏小樱的经验随笔

Codeforces 706B Interesting drink

B. Interesting drink time limit per test:2 seconds memory limit per test:256 meg...

30880
来自专栏大内老A

Delegate如何进行类型转换?

我们知道对于两个不具有继承关系的两个类型,如果没有为它们定义转换器,两这之间的类型转换是不允许的,Delegate也是如此。但是有时候我们却希望“兼容”的两种D...

21580
来自专栏木宛城主

SharePoint CAML In Action——Part II

在SharePoint中,相对于Linq to SharePoint而言,CAML是轻量化的。当然缺点也是显而易见的,"Hard Code"有时会让你抓狂。在实...

20550
来自专栏程序员的SOD蜜

不使用反射的实体类方案

看过很多ORM的实体类方案,大多是用反射来读数据库数据,这样当频繁操作实体类的时候效率很低,我借鉴了一位朋友的思路,采用.NET 2.0的泛型技术,为实体类提供...

22880
来自专栏更流畅、简洁的软件开发方式

我的数据访问函数库的源代码(三)——返回结构数组

/* 2008 4 25 更新 */ 我的数据访问函数库的源码。整个类有1400行,原先就是分开来写的,现在更新后还是分开来发一下吧。 第三部分:返回结构 ...

21460
来自专栏草根专栏

使用xUnit为.net core程序进行单元测试(2)

下面有一点点内容是重叠的.... String Assert 测试string是否相等: [Fact] public void ...

56770
来自专栏行者常至

019.数据交换格式:Json、XML

数据交换格式中,最核心的就是Json和XML。 其中,Json是一种轻量级数据交换格式,XML是一种重量级的数据交换格式。 相比于xml这种数据交换格式来说...

20730
来自专栏逸鹏说道

C# 温故而知新:Stream篇(三)

TextWriter 和 StreamWriter 目录: 为何介绍TextWriter? TextWriter的构造,常用属性和方法 IFormatProvi...

35870
来自专栏风口上的猪的文章

.NET面试题系列[14] - LINQ to SQL与IQueryable

"理解IQueryable的最简单方式就是,把它看作一个查询,在执行的时候,将会生成结果序列。" - Jon Skeet

16910

扫码关注云+社区

领取腾讯云代金券