【发布时间】:2011-02-02 00:41:45
【问题描述】:
我有一个收藏:
List<Car> cars = new List<Car>();
汽车由其属性 CarCode 唯一标识。
我的收藏中有三辆汽车,其中两辆具有相同的 CarCode。
如何使用 LINQ 将此集合转换为具有唯一 CarCodes 的汽车?
【问题讨论】:
我有一个收藏:
List<Car> cars = new List<Car>();
汽车由其属性 CarCode 唯一标识。
我的收藏中有三辆汽车,其中两辆具有相同的 CarCode。
如何使用 LINQ 将此集合转换为具有唯一 CarCodes 的汽车?
【问题讨论】:
您无法在对象集合上有效地使用Distinct(无需额外工作)。我会解释原因。
它使用默认的相等比较器
Default来比较值。
对于对象,这意味着它使用默认方程式方法来比较对象 (source)。那是在他们的哈希码上。并且由于您的对象没有实现 GetHashCode() 和 Equals 方法,它会检查对象的引用,它们并不不同。
【讨论】:
使用MoreLINQ,它有一个DistinctBy 方法:)
IEnumerable<Car> distinctCars = cars.DistinctBy(car => car.CarCode);
(请注意,这仅适用于 LINQ to Objects。)
【讨论】:
NO_HASHSET 是真的,那是你的版本......)?非常感谢!
IQueryable<T>一起使用并尝试DistinctBy它并因此查询整个该死的表......这不是很容易出错?再次感谢您的快速回复!
System 下编写代码感到紧张,因为这给人一种“官方”的错误印象。但是你的口味可能会有所不同,当然:)
我认为在性能方面(或任何方面)的最佳选择是使用 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;
}
}
【讨论】:
您可以查看我的 PowerfulExtensions 库。目前它还处于非常年轻的阶段,但您已经可以在任意数量的属性上使用 Distinct、Union、Intersect、Except 等方法;
这是你使用它的方式:
using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);
【讨论】:
myList.Distinct(x => x.ID) 吗?
与 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);
【讨论】:
完成同样事情的另一种方法...
List<Car> distinticBy = cars
.Select(car => car.CarCode)
.Distinct()
.Select(code => cars.First(car => car.CarCode == code))
.ToList();
可以创建一个扩展方法以更通用的方式执行此操作。如果有人可以针对 GroupBy 方法评估这种“DistinctBy”的性能,那将会很有趣。
【讨论】:
Select 将是 O(n*m) 操作,因此无法很好地扩展。如果有很多重复项,即如果第一个 Select 的结果是原始集合的一小部分,它可能会表现得更好。
您可以实现一个 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());
【讨论】:
cars.Distinct(new GenericEqualityComparer<Car>((a,b) => a.CarCode == b.CarCode, x => x.CarCode.GetHashCode()))。我过去使用过这种方法,因为它有时会在执行一次性 Distinct 时增加价值。
您可以使用分组,并从每个组中获取第一辆车:
List<Car> distinct =
cars
.GroupBy(car => car.CarCode)
.Select(g => g.First())
.ToList();
【讨论】:
First() 在这个用例中完全没问题。