Отличается свойством класса с LINQ



у меня есть коллекция:



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


автомобили однозначно идентифицируются по их свойству CarCode.



у меня есть три автомобиля в коллекции, и два с одинаковыми каркасами.



Как я могу использовать LINQ для преобразования этой коллекции в автомобили с уникальными Каркодами?

577   9  

9 ответов:

вы можете использовать группировку и получить первый автомобиль из каждой группы:

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

использовать MoreLINQ, которая имеет DistinctBy способ :)

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

(это только для LINQ объектов, заметьте.)

тот же подход, что и 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);

вы можете реализовать IEqualityComparer и использовать его в своем отдельном расширении.

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

другой метод расширения для 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;
        }
    }

Я думаю, что лучший вариант с точки зрения производительности (или в любых терминах) заключается в различном использовании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();
    }
}

вы не можете эффективно использовать Distinct на коллекции объектов (без дополнительной работы). Я объясню, почему.

в документации написано:

используется компаратор проверки на равенство по умолчанию, Default, чтобы сравнить значения.

для объектов, это означает, что он использует метод уравнения по умолчанию для сравнения объектов (источник). То есть на их хэш-код. И поскольку ваши объекты не реализуют GetHashCode() и Equals методы, он будет проверять на ссылку объекта, которые не различны.

другой способ сделать то же самое...

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

можно создать метод расширения, чтобы сделать это в более общем виде. Было бы интересно, если бы кто-то мог оценить эффективность этого "DistinctBy" против подхода GroupBy.

вы можете проверить мои мощные расширения библиотека. В настоящее время он находится на очень молодой стадии, но уже можно использовать такие методы, как Distinct, Union, Intersect, за исключением любого количества свойств;

вот как вы его используете:

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

Comments

    Ничего не найдено.