# LINQ 小知識 `IEquatable<T>`, `IEqualityComparer<T>` ## LINQ 的 `Distinct()`, `Except()`, `Intersect()`, `Union()` 沒有得到預想的結果 在下面一段Code中 LINQ To Object 用 `Enumerable.Distinct()` Method, `Enumerable.Except() Method`, `Enumerable.Union() Method` 為甚麼資料都沒有去除重複,取差集, 聯集 資料也沒有得到預想的結果 ``` csharp= /// <summary> /// Sets the engagement team members. /// </summary> private void SetEngagementTeamMembers() { var teamMemberEngagementNumberEqualityComparer = new EngagementTeamMemberEngagementNumberEqualityComparer(); var engagementTeamMembers = GetEngagementTeamMember(teamMemberEngagementNumberEqualityComparer) .ToList(); var engagementMemberByTimeSheet = GetEngagementMemberByTimeSheet(teamMemberEngagementNumberEqualityComparer) .ToList(); var allTeamMembers = engagementTeamMembers .Union(engagementMemberByTimeSheet, teamMemberEngagementNumberEqualityComparer); var existedEngagementTeamMembers = GetExistedEngagementTeamMembers() .Distinct(teamMemberEngagementNumberEqualityComparer); var allNeedAddTeamMembers = allTeamMembers .Except(existedEngagementTeamMembers, teamMemberEngagementNumberEqualityComparer) .ToList(); var teamMembersByTimeSheet = allNeedAddTeamMembers .Except(engagementTeamMembers, teamMemberEngagementNumberEqualityComparer) .ToList(); var needAddTeamMembers = allNeedAddTeamMembers .Except(teamMembersByTimeSheet, teamMemberEngagementNumberEqualityComparer) .ToList(); EngagementTeamMembers = needAddTeamMembers .GroupBy(c => c.EngagementNumber) .ToDictionary(c => c.Key, h => h.Distinct(teamMemberEngagementNumberEqualityComparer).ToList()); EngagementTimeSheetUsers = teamMembersByTimeSheet .GroupBy(c => c.EngagementNumber) .ToDictionary(c => c.Key, h => h.Distinct(teamMemberEngagementNumberEqualityComparer).ToList()); } ``` 原因是在 `Distinct<TSource>(IEnumerable<TSource>)` Returns distinct elements from a sequence by **using the default equality comparer to compare values**. `Except<TSource>(IEnumerable<TSource>, IEnumerable<TSource>)` Produces the set difference of two sequences by **using the default equality comparer to compare values**. 如果是比較`Object`, `Reference types` 的物件,預設是用 **memory addresses** 來做比較 以下的 Code `member1`, `member2` 是不相等的 ``` csharp= class Program { static void Main(String[] args) { var member1 = new EngagementTeamMember(); member1.EngagementNumber = "0000123456"; member1.EmployeeId = "13893"; var member2 = new EngagementTeamMember(); member2.EngagementNumber = "0000123456"; member2.EmployeeId = "13893"; Console.WriteLine(p1.Equals(p2)); // false Console.WriteLine(p1 == p2); // false Console.ReadKey(); } } ``` 判斷相等的方式, 就與想像中的不同 `Distinct()`, `Except()`, `Intersect()`, `Union()` 會出現想像之外的結果, 也就不奇怪了 #### Reference url - [== Operator and Reference Types in C#](https://www.c-sharpcorner.com/article/story-of-equality-in-net-part-five/) ## 如何用定義 Reference types 相等 1. 繼承 `IEquatable<T>` Interface 2. 使用 建構子 `constructor` 設定 屬性 `properties` 3. 並實作 `Equals()` Method 4. 覆寫 Object.GetHashCode,以便有實值相等的兩個物件產生相同的雜湊碼。 ### 繼承 `IEquatable<T>` ``` csharp= protected class EngagementTeamMember : IEquatable<EngagementTeamMember> { public string EngagementNumber { get; } public string EmployeeId { get; } public EngagementTeamMember(string engagementNumber, string employeeId) //Set the properties in the constructor { EngagementNumber = engagementNumber; EmployeeId = employeeId; } public bool Equals(EngagementTeamMember other) //providing a type-specific Equals method. { return other != null && (EngagementNumber == other.EngagementNumber && EmployeeId == other.EmployeeId); } public override int GetHashCode() //Override Object.GetHashCode so that two objects that have value equality produce the same hash code. { return new { EmployeeId, EngagementNumber }.GetHashCode(); } } ``` #### Reference url - [如何:定義類型的實值相等 (C# 程式設計指南)](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type) 這樣修改完在``相等的判斷就沒有會依照想像的方式回傳結果 `Distinct()`, `Except()`, `Intersect()`, `Union()` 也可以正常運作了 ## But 遇到不同的情境, 需要改變判斷相等的條件怎麼辦 上面已經定義了,如果兩個案件 `EngagementNumber` 和 `EmployeeId` 相同, 才會認為相同 今天要是有一個特別的需求,在這個情境下只要 `EngagementNumber` 相等, 兩個物件就算相等 ### 使用 `IEqualityComparer<T>` 在各別情境做比較 新增一個 `Class` `EngagementTeamMemberEngagementNumberEqualityComparer` 繼承 `IEqualityComparer<EngagementTeamMember>` 在做 `LINQ` 做 `Distinct()`, `Except()`, `Intersect()`, `Union()` 使用多載的方式, 把 `IEqualityComparer<T>` 傳入 - `Except<TSource>(IEnumerable<TSource>, IEnumerable<TSource>, IEqualityComparer<TSource>)` - `Distinct<TSource>(IEnumerable<TSource>, IEqualityComparer<TSource>)` - `Union<TSource>(IEnumerable<TSource>, IEnumerable<TSource>, IEqualityComparer<TSource>)` - `Intersect<TSource>(IEnumerable<TSource>, IEnumerable<TSource>, IEqualityComparer<TSource>)` 就會用 `IEqualityComparer<T>` 定義的方式判斷 ``` csharp= protected class EngagementTeamMember : IEquatable<EngagementTeamMember> { public string EngagementNumber { get; } public string EmployeeId { get; } public EngagementTeamMember(string engagementNumber, string employeeId) //Set the properties in the constructor { EngagementNumber = engagementNumber; EmployeeId = employeeId; } public bool Equals(EngagementTeamMember other) //providing a type-specific Equals method. { return other != null && (EngagementNumber == other.EngagementNumber && EmployeeId == other.EmployeeId); } public override int GetHashCode() //Override Object.GetHashCode so that two objects that have value equality produce the same hash code. { return new { EmployeeId, EngagementNumber }.GetHashCode(); } } protected class EngagementTeamMemberEngagementNumberEqualityComparer : IEqualityComparer<EngagementTeamMember> { public bool Equals(EngagementTeamMember x, EngagementTeamMember y) { return y != null && x != null && x.EngagementNumber == y.EngagementNumber; } public int GetHashCode(EngagementTeamMember obj) { return obj.EngagementNumber.GetHashCode(); } } ``` ``` csharp= /// <summary> /// Sets the engagement team members. /// </summary> public partial class DeclarationMainDataService { private void SetEngagementTeamMembers() { var teamMemberEqualityComparer = new EngagementTeamMemberEngagementNumberEqualityComparer(); var engagementTeamMembers = GetEngagementTeamMember(teamMemberEqualityComparer) .ToList(); var engagementMemberByTimeSheet = GetEngagementMemberByTimeSheet(teamMemberEqualityComparer) .ToList(); var allTeamMembers = engagementTeamMembers .Union(engagementMemberByTimeSheet, teamMemberEqualityComparer); var existedEngagementTeamMembers = GetExistedEngagementTeamMembers() .Distinct(teamMemberEqualityComparer); var allNeedAddTeamMembers = allTeamMembers .Except(existedEngagementTeamMembers, teamMemberEqualityComparer) .ToList(); var teamMembersByTimeSheet = allNeedAddTeamMembers .Except(engagementTeamMembers, teamMemberEqualityComparer) .ToList(); var needAddTeamMembers = allNeedAddTeamMembers .Except(teamMembersByTimeSheet, teamMemberEqualityComparer) .ToList(); EngagementTeamMembers = needAddTeamMembers .GroupBy(c => c.EngagementNumber) .ToDictionary(c => c.Key, h => h.Distinct().ToList()); EngagementTimeSheetUsers = teamMembersByTimeSheet .GroupBy(c => c.EngagementNumber) .ToDictionary(c => c.Key, h => h.Distinct().ToList()); } /// <summary> /// Gets the engagement team member. /// </summary> /// <param name="teamMemberEqualityComparer"></param> /// <returns>IQueryable&lt;EngagementTeamMember&gt;.</returns> private IEnumerable<EngagementTeamMember> GetEngagementTeamMember(EngagementTeamMemberEqualityComparer teamMemberEqualityComparer) { var engagementNumbers = EntityNumberKey.Select(c => c.EngagementNumber).ToList(); var tempEngagementTeamMembers = new List<EngagementTeamMember>(); var resultTeamMember = GetEngagementTeamMembersFromTeamMemberTable(engagementNumbers); var resultEngagementTeamMembers = GetEngagementHeaderMemberPersonnelNumber(engagementNumbers); tempEngagementTeamMembers.AddRange(resultTeamMember); tempEngagementTeamMembers.AddRange(resultEngagementTeamMembers); return tempEngagementTeamMembers.Distinct(teamMemberEqualityComparer); } } ``` #### Reference url - [MSDN Enumerable.Distinct Method](https://docs.microsoft.com/zh-tw/dotnet/api/system.linq.enumerable.distinct?view=netframework-4.8) - [MSDN Enumerable.Except Method](https://docs.microsoft.com/zh-tw/dotnet/api/system.linq.enumerable.except?view=netframework-4.8) - [MSDN Enumerable.Union Method](https://docs.microsoft.com/zh-tw/dotnet/api/system.linq.enumerable.union?view=netframework-4.8) - [MSDN Enumerable.Intersect Method](https://docs.microsoft.com/zh-tw/dotnet/api/system.linq.enumerable.intersect?view=netframework-4.8)