Ответ 1
Вы можете использовать группировку и получить первый автомобиль из каждой группы:
List<Car> distinct =
cars
.GroupBy(car => car.CarCode)
.Select(g => g.First())
.ToList();
У меня есть коллекция:
List<Car> cars = new List<Car>();
Автомобили однозначно идентифицируются по их свойству CarCode
.
У меня есть три машины в коллекции и два с идентичными CarCodes.
Как я могу использовать LINQ для преобразования этой коллекции в Cars с уникальными CarCodes?
Вы можете использовать группировку и получить первый автомобиль из каждой группы:
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 и использовать его в своем расширении 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());
Я думаю, что лучший вариант в плане производительности (или в любых выражениях) - это отличиться, используя интерфейс 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();
}
}
Другой метод расширения для 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<TSource>.</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;
}
}
Еще один способ сделать то же самое...
List<Car> distinticBy = cars
.Select(car => car.CarCode)
.Distinct()
.Select(code => cars.First(car => car.CarCode == code))
.ToList();
Можно создать метод расширения, чтобы сделать это более общим способом. Было бы интересно, если бы кто-то мог оценить эффективность этого "DistinctBy" против подхода GroupBy.
Вы можете проверить мою библиотеку PowerfulExtensions. В настоящее время он находится на очень молодой стадии, но уже вы можете использовать такие методы, как Distinct, Union, Intersect, за исключением любого количества свойств;
Вот как вы его используете:
using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);
Вы не можете эффективно использовать Distinct
в коллекции объектов (без дополнительной работы). Я объясню, почему.
Для сравнения значений используется сопоставитель равенства по умолчанию
Default
.
Для объектов, которые означают, что он использует метод уравнения по умолчанию для сравнения объектов (source). То есть на их хэш-код. И поскольку ваши объекты не реализуют методы GetHashCode()
и Equals
, он проверяет ссылку на объект, который не отличается.