編寫高質量代碼改善C#程序的157個建議[IEnumerable 和IQueryable 、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等方法,這樣才能為我們的應用帶來高效性,而不會讓時間浪費在一些無效的迭代中。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM