[C#][LINQ] 從參考型別的列舉中取得某些欄位不重複的物件與效率比較

  • 2733
  • 0

摘要: 這篇只會提到使用 LINQ 內建的方式(不用額外自己寫擴充方法),來取得列舉中不重複的類別(參考型別)項目,

這篇只會提到使用 LINQ 內建的方式(不用額外自己寫擴充方法),來取得列舉中不重複的類別(參考型別)項目

最常見的是 Enumerable 的 Distinct 與 GroupBy 擴充方法,

從理論上來看,這 Distinct 效率會比 GroupBy 還高,

但會隨著數量越多,兩者的效率會慢慢接近,但 Distinct 還是會比較快的,

原因是 Distinct 的概念就像每個 Key 只對應一個 Value,而 GroupBy 的概念就像每個 Key 對應一群 Value,

這樣看起來 GroupBy 需要更多的空間與方法來將 Value 放置某個 Value 集合中。

 

為什麼要提到是參考型別

由於實值型別(數值、布林等)是可以直接做比較的或結構( struct )也會直接比較所有成員,

所以非參考型別的,我們就沒必要去計較使用哪種方法,因為用 Distinct 就能比較,

除非有特定的比較邏輯,才需要自己實作 IEqualityComparer,

這次示範的 Distinct 方法是需要額外在自己實作 IEqualityComparer 介面或實作 EqualityComparer 抽象類別才能比較特定欄位,

因為使用的是參考型別,若一開始在來源的列舉中所有的參考型別本來就是 new 出來的,

也就是列舉中每個參考型別在堆疊中的位址都是不一樣,那再怎麼對參考型別本身( this )做 Distinct 或 GroupBy 都是不會變的列舉,

原因就是參考型別是以堆疊中的位址來做比較,如果是相同的參考型別,那物件位址一定會一樣,

所以下面示範不重複的方式,是針對類別成員中的某幾個欄位來做比較,

例如你想顯示員工生日分佈在哪些日期,就能只對員工類別的生日欄位成員來做比較,

接下來看個比較兩個欄位的程式碼範例,使用 Console 作為示範:

        // dotblogs: jgame2012 
        static void Main(string[] args)
        {

            var now = DateTime.Now;
            // 測試資料,ToList是為了不把 new A() 的時間也算進來
            var range = Enumerable.Range(0, 10000).Select( value => new A() {  ID = value, Time = now.AddMilliseconds(value)}).ToList();
            var groupbyList = new List();    // 儲存 GroupBy  十次計算的時間
            var distinceList = new List();   // 儲存 Distinct 十次計算的時間

            // 精確計算時間用
            var sw = new Stopwatch();
            sw.Restart();

            for (int i = 0; i < 10; i++)
            {
                sw.Restart();
                var c1 = range.GroupBy(item => new { item.ID, item.Time }).Select(g => g.First()).Count();  // 取得用 GroupBy 不重複的數量
                groupbyList.Add(sw.ElapsedMilliseconds);

                sw.Restart();
                var c2 = range.Distinct(new AComparer()).Count();                           // 取得用 Distinct 不重複的數量
                distinceList.Add(sw.ElapsedMilliseconds);
            }

            Console.WriteLine("Group By 耗費平均時間: {0}ms", groupbyList.Average());
            Console.WriteLine("Distinct 耗費平均時間: {0}ms", distinceList.Average());
            
            Console.ReadKey();
        }

        class A
        {
            public int ID { get; set; }
            public DateTime Time { get; set; }

        }

        class AComparer : IEqualityComparer
        {
            public bool Equals(A x, A y)
            {
                return x.ID == y.ID && x.Time == y.Time;
            }

            public int GetHashCode(A obj)
            {
                return obj.ID.GetHashCode() ^ obj.Time.GetHashCode();
            }
        }

結果顯示:

 

雖然取得不重複項目的方法很多,但這篇主要是介紹 LINQ 常用不重複項目的方法,

而且是針對類別(參考型別),而非實值型別、結構的來介紹這兩個存在於 System.Linq.Enumerable 下的擴充方法 ,

當然你不一定要這麼做,想用 HashSet 自己實作,或者想用其他方式達到不重複都可,

由於這是程式碼範例,不應該直接複製貼上就用,如果你正在開發專案中,你應該必須有 LINQ 的基礎,

原因是若不只單單計算總數,還需要顯示每個一個不重複的項目,你甚至要懂得利用列舉的延遲特性,

先 ToList() 後,再用 ICollection 的 Count 屬性來取得總數,

當然並非一定是這樣寫,同時沒有一定要用 Distinct,

只要你知道列舉數量不多,又不想額外產生一個類別,你大可以使用 GroupBy 又快又方便,

記住,如何使用是看你的應用,分析需求的穩定、維護性、效率,如何取捨才是使用哪種方法的重點。

 

 

以上介紹若有觀念錯誤或需要修正的地方,煩請留言告知,轉載請附上原網址。

另外請問有人知道程式碼中有<long>,結果程式碼最下面文章自己跑出</long>的問題要如何解決嗎?