將資料篩選重覆也算是一個常見的課題,尤其是記憶體中的資料未必如SQL語法用個distinct就解決;不過在考慮如何篩選記憶體中的資料重覆前,須先了解資料重覆的標準是什麼,意即物件的相等判斷為何。
對此,以下準備了兩種資料類型進行比對:實值型別的int常數、與參考型別的自定義class MediaMsg。
※ 以下範例是筆者建構的一個MMS多媒體簡訊的範例:
其執行結果如下:
int常數的相等不出意外,但物件的相等判斷上,哪怕是相同的型別相同的值、甚至是透過Clone的方式產生,這些物件都不會相等;因為物件是參考類型,故在程式面上相等判斷是以reference為主。
如下圖,如果直接設定mms2為mms1,則物件相等的判斷上就會是True。
然而對人來說,資料重覆的判斷卻是以資料內容來決定的。如範例中的MediaMsg類型,即便資料欄位好幾個,但對收到簡訊的人來說,多媒體簡訊重覆的判斷依據卻只有簡訊內容、多媒體簡訊的圖檔、多媒體簡訊的主旨三者(但程式判斷上須加上電話號碼本身),其它如資料表的流水號不過是資料庫、系統程式紀錄使用,對使用者而言並不具備多少意義。
所以當今日有MediaMsg物件清單資料時,該如何判斷哪些資料重覆呢?如果會用Linq的人,就會知道使用Distinct方法進行篩選,但Distinct方法預設也是使用相等比較子來進行比較,也就是說如同前述物件資料的比較會以reference為主。
上圖範例中筆者建構10筆MediaMsg物件資料,以人的標準是簡訊內容、多媒體簡訊的圖檔、多媒體簡訊的主旨、電話號碼等四項資料均相同才視為相同簡訊,故在篩選相同的重覆簡訊過後,期望值是留下8筆資料(電話號碼不同、或簡訊內容不同均可視為不重覆的簡訊),但使用Distinct方法卻依舊是10筆資料。因此可以知道,預設的比較是無法使用的,必須自定義一個判斷條件告訴程式資料重覆的判斷依據。
以Distinct方法來說,是可以傳入IEqualityComparer<TSource>的物件告知Distinct在比較物件相等時的判斷條件,故只需實作一個IEqualityComparer<MediaMsg>、再傳入給Distinct即可;再次執行後,就可以看到10筆資料確實只剩下8筆,且透過中斷點檢查資料內容亦可確認簡訊內容、多媒體簡訊的圖檔、多媒體簡訊的主旨、電話號碼等四項資料中至少有一欄資料與其它資料不同。
另外在此補充說明:實作IEqualityComparer<MediaMsg>的方法除了直接繼承該interface以外,亦可考慮繼承EqualityComparer<MediaMsg>以實作,由於EqualityComparer<T>是提供IEqualityComparer<T>的實作基底類別,故程式碼方面實做方法與使用方法均相同,且若實作EqualityComparer<T>的話,未覆寫Equals方法的預設相等比較還可透過泛型類別的Default屬性提供。
※ 對本文主題而言,並不會需要預設相等比較、故也不會用到泛型類別的Default屬性,但若有其它需求情境,不妨可多加考量。
EqualityComparer<T>除了可以用在設定Distinct比較判斷條件以外,亦可用在Dictionary、HashSet等方面;其中,Dictionary也是眾多在篩選重複資料時使用的方法之一,尤其是Dictionary還提供ContainsKey方法可以用來判斷該物件資料是否已經存在。
Reference:
- Object.Equals 方法:https://msdn.microsoft.com/zh-tw/library/bsc2ak47(v=vs.110).aspx
- ReferenceEquals 方法:https://msdn.microsoft.com/zh-tw/library/system.object.referenceequals(v=vs.110).aspx
- Enumerable.Distinct 方法:https://msdn.microsoft.com/zh-tw/library/system.linq.enumerable.distinct(v=vs.100).aspx
- IEqualityComparer<T> 介面:https://msdn.microsoft.com/zh-tw/library/ms132151(v=vs.100).aspx
- EqualityComparer<T> 類別:https://msdn.microsoft.com/zh-tw/library/ms132123(v=vs.100).aspx