在LINQ的世界裡,若在一般的情況下要比對兩個資料的差集,通常我們會直觀用.Any搭配.Contains直接做掉(詳見文末相關參考),但其實這樣的方式只適用於數百筆資料量少的情況之下,若是資料上看數萬、數十萬甚至更多筆資料,那麼我就完全不建議採用這種方式,因為這種方式產生的運算量將形成O(n x m)的笛卡爾乘積運算(Cartesian product),若採用Except()方法可以將運算量降低至O(n + m),相較之下速度自然是快到爆炸了。
這篇文章將會針對直觀的寫法,以及Except()的兩種寫法進行羅列比較,大家可以自行看看喜歡哪一種寫法,我個人的建議如果是真的遇到要使用Except()的情境了,那應該採用實作IEqualityComparer比較器介面來完成,來達成最佳的效能(但是程式碼會比較囉嗦)。
首先先定義ORM資料類別,並創造出兩個準備進行差集的資料集合。母體讓他直接攻上十萬筆且故意採用數值與文字混合,來進行效能的比較。
List<Student> AllStudents = new List<Student>(); for (int i = 0; i < 100000; i++) { AllStudents.Add(new Student() { ID = i, Name = $"ABC{i}" }); } List<Student> BadStudents = new List<Student>(); for (int i = 75000; i < 90000; i++) { BadStudents.Add(new Student() { ID = i, Name = $"ABC{i}" }); } public class Student { public int ID { get; set; } public string Name { get; set; } }
第一種:LINQ差集直觀寫法(效能最差)
var MS = AllStudents .Where(x => !BadStudents.Any(y => y.ID == x.ID && y.Name == x.Name)) .ToList();
第二種:LINQ差集Except()偷懶寫法(效能次之)
var MS = AllStudents .Select(x => new {x.ID, x.Name }) .Except(BadStudents.Select(x => new { x.ID, x.Name })) .ToList();
第三種:LINQ差集Except()完整寫法(效能最佳)
var MS = AllStudents .Except(BadStudents, new StudentComparer()) .ToList(); //實作比較器介面 public class StudentComparer : IEqualityComparer<Student> { public bool Equals(Student x, Student y) { if (object.ReferenceEquals(x, y)) { return true; } if (object.ReferenceEquals(x, null) || object.ReferenceEquals(y, null)) { return false; } return x.ID == y.ID && x.Name == y.Name; } public int GetHashCode(Student obj) { if (obj == null) { return 0; } int IDHashCode = obj.ID.GetHashCode(); int NameHashCode = obj.Name == null ? 0 : obj.Name.GetHashCode(); return IDHashCode ^ NameHashCode; } }
當然啦,還有很多種其他五花八門的Except()寫法,但我不想去擴大額外討論。
因為偷懶,在此僅記錄我實驗完成的數據,若不信的人就...自己動手做樂趣多吧。
LINQ差集方法 | 執行時間 | 耗用記憶體 |
---|---|---|
直觀寫法 | 21736ms | 20.0MB |
Except()偷懶寫法 | 51ms | 22.9MB |
Except()完整寫法 | 37ms | 22.6MB |
建議效能要看他增進的百分率,因為如果把比對的資料差距擴大到百萬筆,那執行時間與耗用記憶體的差距會變得非常可觀。