web-dev-qa-db-ja.com

LINQを使用したクラスのプロパティによる区別

コレクションがあります:

List<Car> cars = new List<Car>();

車はプロパティCarCodeによって一意に識別されます。

コレクションには3台の車があり、2台の車は同一のCarCodeを持っています。

LINQを使用して、このコレクションを一意のCarCodeを持つCarsに変換するにはどうすればよいですか?

147
user278618

グループ化を使用して、各グループから最初の車を取得できます。

List<Car> distinct =
  cars
  .GroupBy(car => car.CarCode)
  .Select(g => g.First())
  .ToList();
260
Guffa

MoreLINQ を使用します。これにはDistinctByメソッドがあります:)

IEnumerable<Car> distinctCars = cars.DistinctBy(car => car.CarCode);

(これはLINQ to Objects専用です、気を付けてください。)

113
Jon Skeet

Guffaと同じアプローチですが、拡張方法として:

public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property)
{
    return items.GroupBy(property).Select(x => x.First());
}

使用されます:

var uniqueCars = cars.DistinctBy(x => x.CarCode);
44

IEqualityComparerを実装して、Distinct拡張機能で使用できます。

class CarEqualityComparer : IEqualityComparer<Car>
{
    #region IEqualityComparer<Car> Members

    public bool Equals(Car x, Car y)
    {
        return x.CarCode.Equals(y.CarCode);
    }

    public int GetHashCode(Car obj)
    {
        return obj.CarCode.GetHashCode();
    }

    #endregion
}

その後

var uniqueCars = cars.Distinct(new CarEqualityComparer());
28
Anthony Pegram

GroupByを使用しないLinq-to-Objectsの別の拡張メソッド:

    /// <summary>
    /// Returns the set of items, made distinct by the selected value.
    /// </summary>
    /// <typeparam name="TSource">The type of the source.</typeparam>
    /// <typeparam name="TResult">The type of the result.</typeparam>
    /// <param name="source">The source collection.</param>
    /// <param name="selector">A function that selects a value to determine unique results.</param>
    /// <returns>IEnumerable&lt;TSource&gt;.</returns>
    public static IEnumerable<TSource> Distinct<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
    {
        HashSet<TResult> set = new HashSet<TResult>();

        foreach(var item in source)
        {
            var selectedValue = selector(item);

            if (set.Add(selectedValue))
                yield return item;
        }
    }
7
Luke Puplett

パフォーマンスの条件(または任意の条件)で最適なオプションは、IEqualityComparerインターフェイスを使用して区別することです。

クラスごとに新しい比較演算子を実装するのは面倒で、定型的なコードを生成します。

そのため、ここでは、リフレクションを使用する任意のクラスに対してオンザフライで新しいIEqualityComparerを生成する拡張メソッドがあります。

使用法:

var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray();

拡張メソッドコード

public static class LinqExtensions
{
    public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property)
    {
        GeneralPropertyComparer<T, TKey> comparer = new GeneralPropertyComparer<T,TKey>(property);
        return items.Distinct(comparer);
    }   
}
public class GeneralPropertyComparer<T,TKey> : IEqualityComparer<T>
{
    private Func<T, TKey> expr { get; set; }
    public GeneralPropertyComparer (Func<T, TKey> expr)
    {
        this.expr = expr;
    }
    public bool Equals(T left, T right)
    {
        var leftProp = expr.Invoke(left);
        var rightProp = expr.Invoke(right);
        if (leftProp == null && rightProp == null)
            return true;
        else if (leftProp == null ^ rightProp == null)
            return false;
        else
            return leftProp.Equals(rightProp);
    }
    public int GetHashCode(T obj)
    {
        var prop = expr.Invoke(obj);
        return (prop==null)? 0:prop.GetHashCode();
    }
}
5

オブジェクトのコレクションでDistinctを効果的に使用することはできません(追加作業なし)。理由を説明します。

ドキュメントによると

デフォルトの等値比較子Defaultを使用して、値を比較します。

オブジェクトの場合、デフォルトの方程式法を使用してオブジェクトを比較することを意味します( source )。それは彼らのハッシュコードにあります。また、オブジェクトはGetHashCode()メソッドとEqualsメソッドを実装していないため、オブジェクトの参照を確認しますが、これらは明確ではありません。

2
Patrick Hofman

同じことを達成する別の方法...

List<Car> distinticBy = cars
    .Select(car => car.CarCode)
    .Distinct()
    .Select(code => cars.First(car => car.CarCode == code))
    .ToList();

より一般的な方法でこれを行うための拡張メソッドを作成することが可能です。誰かがGroupByアプローチに対してこの「DistinctBy」のパフォーマンスを評価できれば興味深いでしょう。

1
JwJosefy

PowerfulExtensions ライブラリを確認できます。現在、それは非常に若い段階ですが、任意の数のプロパティを除いて、Distinct、Union、Intersectなどのメソッドを使用できます。

これがあなたの使い方です:

using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);
1
Andrzej Gis