很方便的 Linq的Distinct 的擴展開發


試想如果能寫成下面的樣子,是不是更簡單優雅:
var p1 = products.Distinct(p => p.ID);
var p2 = products.Distinct(p => p.Name);

使用一個簡單的 lambda 作爲參數,也符合 Linq 一貫的風格。
可通過擴展方法實現:

Distinct 擴展方法

首先,創建一個通用比較的類,實現 IEqualityComparer<T> 接口:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Linq;
public class CommonEqualityComparer<T, V> : IEqualityComparer<T>
{
        private Func<T, V> keySelector;
        public CommonEqualityComparer(Func<T, V> keySelector)
        {
                this.keySelector = keySelector;
        }
        public bool Equals(T x, T y)
        {
                return EqualityComparer<V>.Default.Equals(keySelector(x), keySelector(y));
        }
        public int GetHashCode(T obj)
        {
                return EqualityComparer<V>.Default.GetHashCode(keySelector(obj));
        }
}

第 17 行,用到了 EqualityComparer<T> 類,本文最後有簡要說明
藉助上面這個類,Distinct 擴展方法就很好寫了:
public static class DistinctExtensions
{
        public static IEnumerable<T> Distinct<T, V>(this IEnumerable<T> source, Func<T, V> keySelector)
        {
                return source.Distinct(new CommonEqualityComparer<T, V>(keySelector));
        }
}

呵呵,簡單吧!

Distinct 使用示例

根據 ID :
var data1 = new Person[] {
        new Person{ ID = 1, Name = "鶴沖天"},
        new Person{ ID = 1, Name = "ldp"}
};
var ps1 = data1
        .Distinct(p => p.ID)
        .ToArray();

根據 Name:
var data2 = new Person[] {
        new Person{ ID = 1, Name = "鶴沖天"},
        new Person{ ID = 2, Name = "鶴沖天"}
};
var ps2 = data2
        .Distinct(p => p.Name)
        .ToArray();

看了回覆後,我做了些改進,推薦使用下面的方式

改進

回覆中有朋友提到“不區分大小寫地排除重複的字符串”,也不難實現,只需要把上面的代碼改進下就 OK:
CommonEqualityComparer<T, V> 類:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Linq;
public class CommonEqualityComparer<T, V> : IEqualityComparer<T>
{
        private Func<T, V> keySelector;
        private IEqualityComparer<V> comparer;
        public CommonEqualityComparer(Func<T, V> keySelector, IEqualityComparer<V> comparer)
        {
                this.keySelector = keySelector;
                this.comparer = comparer;
        }
        public CommonEqualityComparer(Func<T, V> keySelector)
                : this(keySelector, EqualityComparer<V>.Default)
        {    }
        public bool Equals(T x, T y)
        {
                return comparer.Equals(keySelector(x), keySelector(y));
        }
        public int GetHashCode(T obj)
        {
                return comparer.GetHashCode(keySelector(obj));
        }
}

Distinct 擴展方法
public static class DistinctExtensions
{
        public static IEnumerable<T> Distinct<T, V>(this IEnumerable<T> source, Func<T, V> keySelector)
        {
                return source.Distinct(new CommonEqualityComparer<T, V>(keySelector));
        }
        public static IEnumerable<T> Distinct<T, V>(this IEnumerable<T> source, Func<T, V> keySelector, IEqualityComparer<V> comparer)
        {
                return source.Distinct(new CommonEqualityComparer<T, V>(keySelector, comparer));
        }
}

藉助可選參數,這兩個擴展方法也可以合成一個:
public static IEnumerable<T> Distinct<T, V>(this IEnumerable<T> source, Func<T, V> keySelector,

        IEqualityComparer<V> comparer = EqualityComparer<V>.Default)
{
        return source.Distinct(new CommonEqualityComparer<T, V>(keySelector, comparer));
}

(同樣,CommonEqualityComparer<T, V>類的兩個構造函數也可以合二爲一)

使用示例:

var data3 = new Person[] {
        new Person{ ID = 1, Name = "LDP"},
        new Person{ ID = 2, Name = "ldp"}
};
var ps3 = data3
        .Distinct(p => p.Name, StringComparer.CurrentCultureIgnoreCase)
        .ToArray();

EqualityComparer<T> 類 簡要說明

EqualityComparer<T>爲 IEqualityComparer<T> 泛型接口的實現提供基類,它在 .net 4 中有五個重要的子類,見下圖:p_w_picpath
這五個子類分別用不同類型數據的相等性比較,從類名我們可以略知一二。
這五個子類都是內部類(internal),不能直接訪問,EqualityComparer<T> 類提供一個簡單的屬性 Default。EqualityComparer<T> 會根據傳入的 T 的類型,加載不同的子類,並會予以緩存提高性能。


c#擴展方法奇思妙用》系統文章從 2009 年 08 月開始寫起,到現在一共有了 22 篇,歡迎閱讀:
基礎篇:中文處理string 常用擴展byte 常用擴展Random 擴展Dictionary<TKey, TValue> 擴展WhereIf 擴展IsBetween 通用擴展WhereIf 擴展Distinct 擴展
高級篇:改進 Scottgu 的 "In" 擴展Aggregate擴展其改進Enumerable.Cast<T>應用對擴展進行分組管理ToString(string format) 擴展WinForm 控件選擇器樹”通用遍歷器Type類擴展
變態篇:由Fibonacci數列引出“委託擴展”及“遞推遞歸委託”封裝 if/else、swith/case及whileswitch/case 組擴展string 的翻身革命
性能篇擴展方法性能初測
MVC篇:巧用擴展方法優先級,美化所有頁面TextBoxFor文本框
-------------------
思想火花,照亮世界

像大神致敬:http://www.cnblogs.com/ldp615/archive/2011/08/01/distinct-entension.html  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章