LinqとIEqualityComparerへの疑問

Distinctの引数はラムダ式でのselectorを受け付けてくれない。IEqualityComparerだけなので、抽出のためにわざわざ外部にIEqualityComparerを実装したクラスを作る必要がある。それって、面倒くさいし分かり辛いし、何でここだけ古くさいような仕様なのだろう。C#3.0っぽくない。しょうがないので、単純ですけど汎用的に使えるようなものを作ってみた。

// IEqualityComparer<T>の実装が面倒なのでセレクタ的なものはこれで賄う
public class CompareSelector<T, TKey> : IEqualityComparer<T>
{
    private Func<T, TKey> selector;

    public CompareSelector(Func<T, TKey> selector)
    {
        this.selector = selector;
    }

    public bool Equals(T x, T y)
    {
        return selector(x).Equals(selector(y));
    }

    public int GetHashCode(T obj)
    {
        return selector(obj).GetHashCode();
    }
}

class MyClass
{
    public int MyProperty { get; set; }
}

static void Main(string[] args)
{
    // このクラスのMyPropertyで重複除去したい
    var mc1 = new MyClass { MyProperty = 3 };
    var mc2 = new MyClass { MyProperty = 3 };
    var array = new[] { mc1, mc2 };

    var r1 = array.Distinct().Count();
    Console.WriteLine(r1); // 勿論2です
    // 比較用のIEqualityComparer<T>インスタンスを渡す
    var r2 = array
        .Distinct(new CompareSelector<MyClass, int>(mc => mc.MyProperty))
        .Count();
    Console.WriteLine(r2); // 1です
}

newするから、型を書かなければいけなくてね、記述量が多くて嫌だ。重たい重たい。C#3.0ってのは、もっとライトウェイトじゃなきゃダメなんだ。推論!型推論!しょうがないので、Distinctそのものに拡張メソッドを定義すれば……

public static class ExtensionMethods
{
    public static IEnumerable<T> Distinct<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector)
    {
        return source.Distinct(new CompareSelector<T, TKey>(selector));
    }
}

var r3 = array.Distinct(mc => mc.MyProperty).Count();
Console.WriteLine(r3); // 1になる

ラムダ式で書けるようになる。この調子でIEqualityComparerを使ってるメソッドの全てに拡張メソッドを定義すれば問題なし。しかし準備が面倒。このことは、ForEachが搭載されないことと並ぶLinq最大の謎だと私は思っているのですけど、どうなんでしょうか。何か理由があるのかなあ。とても気になるのだけど……。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive