程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> Linq研究,linqgroupby

Linq研究,linqgroupby

編輯:C#入門知識

Linq研究,linqgroupby


微軟在.NET 3.5中加入了LINQ技術,作為配套改進了C#語言,加入了Lambda表達式,擴展方法,匿名類型等新特性用以支持LINQ。微軟同時提出了要使用聲明式編程,即描述計算規則,而不是描述計算過程。

使用LINQ技術能很好地做到聲明式編程,寫出的代碼表意能力強,可讀性高,避免了以往或其他語言的代碼中充斥大量表意不明的for循環甚至多層循環的問題。不要小看for循環和Where,Select,OrderBy等擴展方法的區別,可以不通過注釋一眼就能看出代碼意圖真的很重要。當看到Java代碼中一大堆的for循環,包括多層循環,又沒有注釋,必須仔細看才能了解代碼作用時,真的很頭大。個人認為LINQ是C#語言區別於其他語言的最顯著的特性,也是最大的優勢之一。

當然現在大多數主流語言都加入了Lambda表達式,從而可以使用類似於LINQ的技術,達到聲明式編程。比如Java語言在Java 8中加入了和C#幾乎一樣的Lambda表達式語法,並加入了Stream API,以達到類似於LINQ的用法。

如此可見,聲明式編程是發展趨勢,既然使用C#,就要多用LINQ,用好LINQ,用對LINQ。不要再寫一堆一堆的for循環了!

要用好LINQ,就要學好LINQ,理解其原理,機制和用法。推薦一個學習和研究LINQ的好工具LINQPad,下面是官網和官網上的截圖。

http://www.linqpad.net/

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace LinqResearch { class Program { static void Main(string[] args) { var list = new List<int> { 2, 1, 6, 4, 3, 5, 7, 8, 10, 9 }; Console.WriteLine("list"); var list1 = list.Select(i => { Console.WriteLine("In select {0}", i); return i * i; }); Console.WriteLine("list1"); var list2 = list1.Where(i => { Console.WriteLine("In where>10 {0}", i); return i > 10; }); Console.WriteLine("list2"); var list3 = list2.Where(i => { Console.WriteLine("In where<60 {0}", i); return i < 60; }); Console.WriteLine("list3"); var list4 = list3.OrderBy(i => { Console.WriteLine("In orderby {0}", i); return i; }); Console.WriteLine("list4"); var list5 = list4.ToList(); Console.WriteLine("list5"); foreach (var i in list5) { Console.WriteLine(i); } } } }

先不要看下面的運行結果,想想打印出的是什麼,然後再看結果,看看和想的一樣嗎?

list
list1
list2
list3
list4
In select 2
In where>10 4
In select 1
In where>10 1
In select 6
In where>10 36
In where<60 36
In select 4
In where>10 16
In where<60 16
In select 3
In where>10 9
In select 5
In where>10 25
In where<60 25
In select 7
In where>10 49
In where<60 49
In select 8
In where>10 64
In where<60 64
In select 10
In where>10 100
In where<60 100
In select 9
In where>10 81
In where<60 81
In orderby 36
In orderby 16
In orderby 25
In orderby 49
list5
16
25
36
49

為什麼先打印出list 到 list4,而沒有進到Lambda裡面?

這是因為LINQ是延時計算的,即只有foreach或ToList時才去做真正計算,前面的Select,Where等語句只是聲明了計算規則,而不進行計算。

這點很重要,如果不明白這點,就會寫出有BUG的代碼,如下面的程序,打印出的是1和2,而不是1。

            var a = 2;
            var list = new List<int> { 1, 2, 3 };
            var list1 = list.Where(i => i < a);
            a = 3;
            foreach (var i in list1)
            {
                Console.WriteLine(i);
            }

後面打印出的為什麼先是select和where交錯,然後是orderby,而不是先select再where,最後orderby?

這時因為Select,Where等這些擴展方法,在聲明計算規則時是有優化的(內部可能通過表達式樹等方法實現),它並不是傻傻的按照原始定義的規則,順序執行,而是以一種優化的方法計算並獲得結果。所以使用 LINQ一般會比自己寫的原始的一大堆for循環性能還高,除非花大量時間優化自己的邏輯(一般不會有這個時間)。

可以看到針對元素2和1,並沒有打印出In where<60 的行,這說明針對這兩個元素,第二個Where裡的代碼並沒有執行,因為第一個Where都沒有通過。在進行完投影(Select)和篩選(Where)後,最後進行排序(OrderBy),只針對篩選後留下的元素執行OrderBy裡面的計算邏輯,一點也不浪費。

上面的程序有人可能會寫成這樣。

            var list = new List<int> { 2, 1, 6, 4, 3, 5, 7, 8, 10, 9 };
            Console.WriteLine("list");
            var list1 = list.Select(i =>
            {
                Console.WriteLine("In select {0}", i);
                return i * i;
            }).ToList();
            Console.WriteLine("list1");
            var list2 = list1.Where(i =>
            {
                Console.WriteLine("In where>10 {0}", i);
                return i > 10;
            }).ToList();
            Console.WriteLine("list2");
            var list3 = list2.Where(i =>
            {
                Console.WriteLine("In where<60 {0}", i);
                return i < 60;
            }).ToList();
            Console.WriteLine("list3");
            var list4 = list3.OrderBy(i =>
            {
                Console.WriteLine("In orderby {0}", i);
                return i;
            }).ToList();
            Console.WriteLine("list4");
            var list5 = list4.ToList();
            Console.WriteLine("list5");
            foreach (var i in list5)
            {
                Console.WriteLine(i);
            }

這樣寫打印出的結果為,

list
In select 2
In select 1
In select 6
In select 4
In select 3
In select 5
In select 7
In select 8
In select 10
In select 9
list1
In where>10 4
In where>10 1
In where>10 36
In where>10 16
In where>10 9
In where>10 25
In where>10 49
In where>10 64
In where>10 100
In where>10 81
list2
In where<60 36
In where<60 16
In where<60 25
In where<60 49
In where<60 64
In where<60 100
In where<60 81
list3
In orderby 36
In orderby 16
In orderby 25
In orderby 49
list4
list5
16
25
36
49

雖然也能得到正確的結果,但是卻是不合理的。因為這樣寫每步都執行計算,並放到集合中,會有很大的性能損耗,失去了使用LINQ的優勢。

何時進行真正計算是個值得思考的問題,多了會增加中間集合的數量,性能不好,少了有可能會有多次重復計算,性能也不好。下文會有說明。

如果使用Resharper插件,會提示出重復迭代(可能會有多次重復計算)的地方,這個功能很好,便於大家分析是否存在問題。

使用Max和Min要小心,Max和Min等聚合運算需要集合中存在值,否則會拋出異常,筆者多次遇到這個問題產生的BUG。

當前面有Where篩選時,後面使用Max或Min不一定是安全的,如下面的代碼會拋出異常。

            var a = 0;
            var list = new List<int> { 1, 2, 3 };
            var min = list.Where(i => i < a).Min();
            Console.WriteLine(min);

如果a來源於外部值,又有大段的邏輯,這樣的BUG不易發現。

解決方法有多種,我們來分析一下,一種方法是可以先調一下Any,再使用Min,代碼如下,

            var a = 0;
            var list = new List<int> { 1, 2, 3 };
            var list2 = list.Where(i => i < a);
            var min = 0;
            if (list2.Any())
            {
                min = list2.Min();
            }
            Console.WriteLine(min);

把代碼改為如下,

            var a = 3;
            var list = new List<int> { 1, 2, 3 };
            var list2 = list.Where(i =>
            {
                Console.WriteLine("In where {0}", i);
                return i < a;
            });
            var min = 0;
            if (list2.Any(i =>
            {
                Console.WriteLine("In any {0}", i);
                return true;
            }))
            {
                min = list2.Min();
            }
            Console.WriteLine(min);

打印結果為,

In where 1
In any 1
In where 1
In where 2
In where 3
1

這樣做有可能對性能影響不大,也有可能較大,取決於where(或前面的其他邏輯)中邏輯的多少和集合中前面不滿足where條件的元素的數量。因為Any確定有就不會繼續執行,但仍有部分重復計算發生。

第二種方法的代碼如下,

            var a = 3;
            var list = new List<int> { 1, 2, 3 };
            var list2 = list.Where(i => i < a).ToList();
            var min = 0;
            if (list2.Any())
            {
                min = list2.Min();
            }
            Console.WriteLine(min);

這種方法不會有重復計算的開銷,但會有數據導入集合的開銷,和第一種比較哪種性能更高值得考慮。

第三種方法的代碼如下,

            var a = 0;
            var list = new List<int> { 1, 2, 3 };
            var list2 = list.Where(i => i < a);
            var min = 0;
            try
            {
                min = list2.Min();
            }
            catch (Exception)
            {
            }
            Console.WriteLine(min);

直接吃掉異常,數據量大時,前面過濾條件計算復雜時,可能這種方法性能最高。

總之,C#開發者,學好LINQ,用好LINQ,你會發現真的很爽的!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved