# 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<EngagementTeamMember>.</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)