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

简介: 前言 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要学习记录以下内容:   建议29、区别LINQ查询中的IEnumerable和IQueryable   建议30、使用LINQ取代集合中的比较器和迭代器   建议31、在LINQ查询中避免不必要的迭代 建议29、区别LINQ查询中的IEnumerable和IQueryable   LINQ查询方法一共提供了两类扩展方法,在System.Linq命名空间下,有两个静态类:     Enumerable类,它针对继承了IEnumerable接口的集合类进行扩展。

前言

本文已更新至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月前
|
C# Windows
C#通过代码实现快捷键编辑
C#通过代码实现快捷键编辑
|
3月前
|
开发框架 .NET 编译器
C# 10.0中Lambda表达式的改进:更简洁、更灵活的代码编写体验
【1月更文挑战第21天】随着C#语言的不断发展,Lambda表达式作为一种简洁、高效的函数式编程工具,在C# 10.0中迎来了重要的改进。本文将详细探讨C# 10.0中Lambda表达式的新特性,包括参数类型的推断增强、自然类型的Lambda参数以及Lambda表达式的属性改进等。这些改进不仅简化了Lambda表达式的编写过程,还提升了代码的可读性和灵活性,为开发者带来了更优质的编程体验。
|
3月前
|
C# 开发者
C# 10.0中的文件范围命名空间:简化代码组织的新方式
【1月更文挑战第18天】C# 10.0引入了文件范围的命名空间,这是一种新的语法糖,用于更简洁地组织和管理代码。文件范围命名空间允许开发者在每个文件的基础上定义命名空间,而无需显式使用花括号包裹整个文件内容。本文将深入探讨文件范围命名空间的工作原理、使用场景以及它们为C#开发者带来的便利。
|
3月前
|
C# 开发者
C# 9.0中的模块初始化器:程序启动的新控制点
【1月更文挑战第14天】本文介绍了C# 9.0中引入的新特性——模块初始化器(Module initializers)。模块初始化器允许开发者在程序集加载时执行特定代码,为类型初始化提供了更细粒度的控制。文章详细阐述了模块初始化器的语法、用途以及与传统类型初始化器的区别,并通过示例代码展示了如何在实际项目中应用这一新特性。
|
3月前
|
编译器 C# 开发者
C# 9.0中的顶级语句:简化程序入口的新特性
【1月更文挑战第13天】本文介绍了C# 9.0中引入的顶级语句(Top-level statements)特性,该特性允许开发者在不使用传统的类和方法结构的情况下编写简洁的程序入口代码。文章详细阐述了顶级语句的语法、使用场景以及与传统程序结构的区别,并通过示例代码展示了其在实际应用中的便捷性。
|
3月前
|
SQL 开发框架 .NET
C#进阶-LINQ实现对集合的增删改查
本篇演示了LINQ在日常开发中的常用操作,实现结果集的增删改查。目前LINQ支持两种语法,我会在每个案例前先用大家熟知的SQL语句表达,再在后面用C#的两种LINQ语法分别实现。LINQ语法第一次接触难免感到陌生,最好的学习方式就是在项目中多去使用,相信会有很多感悟。
34 0
|
1月前
|
开发框架 .NET C#
C#学习相关系列之Linq用法---where和select用法(二)
C#学习相关系列之Linq用法---where和select用法(二)
|
29天前
|
Java C# 开发工具
第一个C#程序
第一个C#程序
12 0
|
1月前
|
开发框架 .NET C#
C#学习相关系列之Linq用法---group和join相关用法(三)
C#学习相关系列之Linq用法---group和join相关用法(三)
|
1月前
|
开发框架 .NET C#
C#学习相关系列之Linq常用方法---排序(一)
C#学习相关系列之Linq常用方法---排序(一)