Cisis - C##

ASP.NET Core 開発者のブログ

LINQ で指定したキーだけ使って Distinct, Except とかする

たとえばこんなてきとうなクラスがあったとして

public class MyClass
{
    public int Id { get; set; }
    public double Value { get; set; }
}

Id だけを見て distinct とかしたい (けど結果は Value もほしいし、Id が重複した場合に Value は順番に依存 (最初に見つかったもの) で構わない) とかいう場合のはなしです。 最近ちょっと似たようなの書くことがあったのでメモ IEqualityComparer 書けばできますが、もうちょっとぱぱっと書きたくて。。。

Distinct

public static IEnumerable<TItem> Distinct<TItem, TKey>(this IEnumerable<TItem> source, Func<TItem, TKey> keySelector)
{
    IEnumerable<TItem> Enumerate()
    {
        var set = new HashSet<TKey>();
        foreach (var item in source)
            if (set.Add(keySelector(item))) yield return item;
    }

    if (source == null)
        throw new ArgumentNullException(nameof(source));

    if (keySelector == null)
        throw new ArgumentNullException(nameof(keySelector));

    return Enumerate();
}
var data = new []
{
    new MyClass { Id = 1, Value = 1.0 },
    new MyClass { Id = 2, Value = 2.0 },
    new MyClass { Id = 1, Value = 0.5 },
};

// {Id = 1, Value = 1.0}, {Id = 2, Value = 2.0}
var result = data.Distinct(x => x.Id);

Except

public static IEnumerable<TItem> Except<TItem, TKey>(this IEnumerable<TItem> first, IEnumerable<TItem> second, Func<TItem, TKey> keySelector)
{
    IEnumerable<TItem> Enumerate()
    {
        var set = new HashSet<TKey>(second.Select(keySelector));
        foreach (var item in first)
            if (set.Add(keySelector(item))) yield return item;
    }

    if (first == null)
        throw new ArgumentNullException(nameof(first));

    if (second == null)
        throw new ArgumentNullException(nameof(second));

    if (keySelector == null)
        throw new ArgumentNullException(nameof(keySelector));

    return Enumerate();
}
var first = new []
{
    new MyClass { Id = 1, Value = 1.0 },
    new MyClass { Id = 2, Value = 2.0 },
    new MyClass { Id = 3, Value = 3.0 },
};

var second = new []
{
    new MyClass { Id = 1, Value = 100.0 }
};

// {Id = 2, Value = 2.0}, {Id = 3, Value = 3.0}
var result = first.Except(second, x => x.Id);

Union

public static IEnumerable<TItem> Union<TItem, TKey>(this IEnumerable<TItem> first, IEnumerable<TItem> second, Func<TItem, TKey> keySelector)
{
    IEnumerable<TItem> Enumerate()
    {
        var set = new HashSet<TKey>();
        foreach (var item in first)
            if (set.Add(keySelector(item))) yield return item;

        foreach (var item in second)
            if (set.Add(keySelector(item))) yield return item;
    }

    if (first == null)
        throw new ArgumentNullException(nameof(first));

    if (second == null)
        throw new ArgumentNullException(nameof(second));

    if (keySelector == null)
        throw new ArgumentNullException(nameof(keySelector));

    return Enumerate();
}
var first = new []
{
    new MyClass { Id = 1, Value = 1.0 },
    new MyClass { Id = 2, Value = 2.0 },
    new MyClass { Id = 3, Value = 3.0 },
};

var second = new []
{
    new MyClass { Id = 1, Value = 100.0 },
    new MyClass { Id = 4, Value = 400.0 },
};

// {Id = 1, Value = 1.0}, {Id = 2, Value = 2.0}, {Id = 3, Value = 3.0}, {Id = 4, Value = 400.0}
var result = first.Union(second, x => x.Id);

Intersect

public static IEnumerable<TItem> Intersect<TItem, TKey>(this IEnumerable<TItem> first, IEnumerable<TItem> second, Func<TItem, TKey> keySelector)
{
    IEnumerable<TItem> Enumerate()
    {
        var set = new HashSet<TKey>(second.Select(keySelector));

        foreach (var item in first)
            if (!set.Add(keySelector(item))) yield return item;
    }

    if (first == null)
        throw new ArgumentNullException(nameof(first));

    if (second == null)
        throw new ArgumentNullException(nameof(second));

    if (keySelector == null)
        throw new ArgumentNullException(nameof(keySelector));

    return Enumerate();
}
var first = new []
{
    new MyClass { Id = 1, Value = 1.0 },
    new MyClass { Id = 2, Value = 2.0 },
    new MyClass { Id = 3, Value = 3.0 },
};

var second = new []
{
    new MyClass { Id = 1, Value = 100.0 },
    new MyClass { Id = 4, Value = 400.0 },
};

// {Id = 1, Value = 1.0}
var result = first.Intersect(second, x => x.Id);