清單序列物件過濾重覆物件
如果要過濾清單序列物件的重覆項目,在 .net 中透過 Distinct 方法就可以很簡潔的完成,例如下面例子:
[TestMethod]
public void Test_Distinct_string()
{
//Arrange
var objBefor = new List<string>() { "a", "a", "b", "c" };
//Act
var objActual = objBefor.Distinct().ToList();
//Assert
var objExpected = new List<string>() { "a", "b", "c" };
CollectionAssert.AreEqual(objExpected, objActual);
}
自訂類別序列過濾重覆物件
但如果清單序列物件的項目類別是自訂類別時則會發生錯誤,舉個例說明, 例如有一個 Empolyee 類別如下,包含 ID 及 Name 兩個屬性。
public class Employee
{
public Employee(string pi_sID, string pi_sName)
{
this.ID = pi_sID;
this.Name = pi_sName;
}
public string ID {get;set;}
public string Name {get;set;}
}
則下面的測試就會驗證失敗。
[TestMethod]
public void Test_Distinct_False()
{
//Arrangy
var objBefor = new List<Employee>
{
new Employee("1", "大寶"),
new Employee("2", "王二"),
new Employee("1", "大寶"),
new Employee("3", "張三"),
new Employee("4", "李四"),
new Employee("5", "李武")
};
//Act
var objActual = objBefor.Distinct().ToList<Employee>();
//Assert
var objExpected = new List<Employee>
{
new Employee("1", "大寶"),
new Employee("2", "王二"),
new Employee("3", "張三"),
new Employee("4", "李四"),
new Employee("5", "李武")
};
CollectionAssert.AreEqual(objExpected, objActual);
}
這是因為 Distinct 方法在比對物件是否「相等」時,是採用類別的 Equals 方法逐項比對,所以在 Employee 沒有覆寫 Equals 方法以自訂比對邏輯時,會採用其母類別的 Equals 方法邏輯,也就是 Object 類別的 Equals 方法,採用比對物件「位址」的方法進行,所以除非重覆的物件是「相同」實體,否則都會被視為不同,所以前面的測試不會通過,但下面的例子中,重覆的情形是「相同」實體,就可以通過驗測。
[TestMethod]
public void Test_Distinct_SAME_Instance()
{
//Arrangy
var objEmployee = new Employee("1", "大寶");
var objBefor = new List<Employee>
{
objEmployee ,
new Employee("2", "王二"),
objEmployee ,
new Employee("3", "張三"),
new Employee("4", "李四"),
new Employee("5", "李武")
};
//Act
var objActual = objBefor.Distinct().ToList<Employee>();
//Assert
var objExpected = new List<Employee>
{
new Employee("1", "大寶"),
new Employee("2", "王二"),
new Employee("3", "張三"),
new Employee("4", "李四"),
new Employee("5", "李武")
};
CollectionAssert.AreEqual(objExpected, objActual);
}
那如果要在「不同」實體之間比對,要怎麼做呢?
透過 IEqualityComarer 處理物件比對邏輯
透過實做清單序列的 Distinct 方法,可以取得不重覆的清單序列,但並不是可以白吃的一餐,畢竟程式無法知道「相等」物件的判斷,所以要提供 Distinct 方法一個判斷工具,而這個判斷工具則需要實做介面 IEqualityComparer<T> 提供必要的方法。
例如下面的 EmployeeComparer 類別就實做 IEqualityComparer<T> 自訂比對邏輯,採用 ID 及 Name 兩個屬性都一致時才視為「相等」實體。
internal class EmployeeComparer : IEqualityComparer<Employee>
{
bool IEqualityComparer<Employee>.Equals(Employee x, Employee y)
{
return x.ID == y.ID && x.Name == y.Name;
}
int IEqualityComparer<Employee>.GetHashCode(Employee obj)
{
return 0;
}
}
下面的例子利用實做 IEQualityComparer<T> 的 EmployeeComparer 類別自訂比對邏輯,如此就可以順利的通過測試。
[TestMethod]
public void Test_Distinct()
{
//Arrang
var objBefor = new List<Employee>
{
new Employee("1", "大寶"),
new Employee("2", "王二"),
new Employee("1", "大寶"),
new Employee("3", "張三"),
new Employee("4", "李四"),
new Employee("5", "李武")
};
//Act
var objComparer = new EmployeeComparer();
var objActual = objBefor.Distinct(objComparer).ToList<Employee>();
//Assert
var objExpected = new List<Employee>
{
new Employee("1", "大寶"),
new Employee("2", "王二"),
new Employee("3", "張三"),
new Employee("4", "李四"),
new Employee("5", "李武")
};
CollectionAssert.AreEqual(objExpected, objActual);
}
結語
透過 Distinct 配合 IEQualityComparer<T> 實做並非達到比對物件是否「相等」此一目的的唯一方法,但使用 Linq 的 Distinct 方法,在閱讀上也相當簡潔易懂,同時將比對的邏輯獨立撰寫,也有利於後續維護及擴充,例如,可以依據不同情境提供不同的比對邏輯,則客戶端可以動態的抽換即可以有不同的判斷結果,維護人員可以各自獨立撰寫等等。
文中各個測試採用的斷言語句可以參考這篇文章。
今天是情人節耶!