Enumerable.Distinctメソッドは、System.Linq名前空間に含まれる拡張メソッドで、配列やコレクションから重複する要素を削除するために使用されます。また、独自の重複判定ロジックを定義することも可能です。
このメソッドには2つのオーバーロードが存在します。
//
// 概要:
// 既定の等値比較子を使用して値を比較し、シーケンスから重複する要素を削除します。
//
// パラメーター:
// source:
// 重複する要素を削除する対象のシーケンス。
//
// 型パラメーター:
// TSource:
// source の要素の型。
//
// 戻り値:
// source の重複する要素が削除された、System.Collections.Generic.IEnumerable<T>。
//
// 例外:
// System.ArgumentNullException:
// source が null です。
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source);
//
// 概要:
// 指定された System.Collections.Generic.IEqualityComparer<T> を使用して値を比較し、シーケンスから重複する要素を削除します。
//
// パラメーター:
// source:
// 重複する要素を削除する対象のシーケンス。
//
// comparer:
// 値を比較するための System.Collections.Generic.IEqualityComparer<T>。
//
// 型パラメーター:
// TSource:
// source の要素の型。
//
// 戻り値:
// source の重複する要素が削除された、System.Collections.Generic.IEnumerable<T>。
//
// 例外:
// System.ArgumentNullException:
// source が null です。
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer);
一つ目のメソッドはパラメーターを必要とせず、二つ目のメソッドはSystem.Collections.Generic.IEqualityComparer<T>の実装オブジェクトを引数として受け取ります。
- 値型要素のコレクションから重複を削除する
var intList = new List<int> { 1, 1, 2, 2, 3, 4, 5, 5 };
intList.Distinct().ToList().ForEach(s => Console.WriteLine(s));
実行結果:1 2 3 4 5
- 参照型要素のコレクションから重複を削除する
まず、Personクラスを定義します。
パラメーターなしのDistinctメソッドを使用して重複を削除します。
var personList = new List<Person>() {
new Person("ジェームス", 1, "バスケットボール"),
new Person("ジェームス", 1, "バスケットボール"),
new Person("コービー", 2, "バスケットボール"),
new Person("カリー", 3, "フットボール"),
new Person("カリー", 3, "ヨガ")
};
personList.Distinct().ToList().ForEach(s => Console.WriteLine(s.ToString()));
実行結果:
このように、重複するレコードは削除されていません。
パラメーターなしのDistinctメソッドは、IEqualityComparerインターフェイスの既定の比較子を使用して比較を行います。参照型の場合、既定の比較子は参照アドレスを比較します。上記のコードでは、コレクション内の各要素は新しいインスタンスであり、参照アドレスが異なるため、重複したレコードとして削除されません。
そのため、二つ目のオーバーロードメソッドを使用する必要があります。
IEqualityComparerインターフェイスを実装する新しいクラスを作成します。GetHashCodeメソッドの実装に注意してください。HashCodeが同じ場合のみ比較が行われます。
public class CustomComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
return x.ID == y.ID; // 重複判定ルールをカスタマイズできます。ここではIDが同じ場合を重複とみなします。
}
public int GetHashCode(Person obj)
{
return obj.ID.GetHashCode();
}
}
そして、以下のように呼び出します。
personList.Distinct(new CustomComparer()).ToList().ForEach(s => Console.WriteLine(s.ToString()));
実行結果:
IDに基づいてコレクションから重複を削除することが成功しました!
- 拡張性のある重複削除メソッドの作成方法
public class GenericComparer<T, C> : IEqualityComparer<T>
{
private Func<T, C> _keySelector;
public GenericComparer(Func<T, C> keySelector)
{
this._keySelector = keySelector;
}
public bool Equals(T x, T y)
{
return EqualityComparer<C>.Default.Equals(_keySelector(x), _keySelector(y));
}
public int GetHashCode(T obj)
{
return EqualityComparer<C>.Default.GetHashCode(this._keySelector(obj));
}
}
public static class CollectionExtensions
{
/// <summary>
/// カスタムキーによる重複削除拡張メソッド
/// </summary>
/// <typeparam name="T">重複を削除するオブジェクトの型</typeparam>
/// <typeparam name="C">カスタムキーの型</typeparam>
/// <param name="source">重複を削除する対象のコレクション</param>
/// <param name="keySelector">重複判定に使用するキーを取得するデリゲート</param>
/// <returns></returns>
public static IEnumerable<T> WithCustomKey<T, C>(this IEnumerable<T> source, Func<T, C> keySelector)
{
return source.Distinct(new GenericComparer<T, C>(keySelector));
}
}
呼び出し方法:
personList.WithCustomKey(p => p.ID).ToList().ForEach(s => Console.WriteLine(s.ToString()));
ジェネリック、デリゲート、拡張メソッドなどの知識が使用されています。あらゆるコレクションの様々な重複削除シナリオに適用可能です。