与LINQ类的属性不同

时间:2010-03-29 12:31:50

标签: c# linq distinct

我有一个集合:

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

汽车由其财产CarCode唯一标识。

我在这个系列中有三辆车,两辆车有相同的CarCodes。

如何使用LINQ将此集合转换为具有唯一CarCodes的汽车?

9 个答案:

答案 0 :(得分:250)

您可以使用分组,并从每个组中获取第一辆车:

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

答案 1 :(得分:113)

使用MoreLINQ,其DistinctBy方法:)

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

(这只适用于LINQ to Objects,请注意。)

答案 2 :(得分:40)

与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);

答案 3 :(得分:28)

您可以实现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());

答案 4 :(得分:6)

Linq-to-Objects的另一种扩展方法,不使用GroupBy:

    /// <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;
        }
    }

答案 5 :(得分:5)

我认为性能方面(或任何术语)的最佳选择是使用 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();
    }
}

答案 6 :(得分:2)

您无法在对象集合上有效使用Distinct(无需额外工作)。我会解释原因。

The documentation says

  

它使用默认的相等比较器Default来比较值。

对于对象,这意味着它使用默认的方程方法来比较对象(source)。这是他们的哈希码。由于您的对象未实现GetHashCode()Equals方法,因此它将检查对象的引用,这些引用不是明确的。

答案 7 :(得分:1)

另一种完成同样事情的方法......

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

可以创建一种扩展方法,以更通用的方式执行此操作。如果有人能够针对GroupBy方法评估这种“DistinctBy”的表现,那将会很有趣。

答案 8 :(得分:1)

您可以查看我的PowerfulExtensions图书馆。目前它处于一个非常年轻的阶段,但你已经可以使用Distinct,Union,Intersect等方法,除了任何数量的属性;

这是您使用它的方式:

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