直到最近,我还在 LINQ 中使用 Distinct 从表中选择不同的类别(枚举)。这工作得很好。
我现在需要在包含类别和国家/地区(均为枚举)的类上区分它。 Distinct 现在不起作用。
我做错了什么?
我相信这篇文章可以解释您的问题: https://web.archive.org/web/20201203072646/http://blog.jordanterrell.com/post/LINQ-Distinct()-does-not-work-as-expected.aspx
上面链接的内容可以总结为Distinct()方法可以通过执行以下操作来替换。
var distinctItems = items
.GroupBy(x => x.PropertyToCompare)
.Select(x => x.First());
尝试 IQualityComparer
public class MyObjEqualityComparer : IEqualityComparer<MyObj>
{
public bool Equals(MyObj x, MyObj y)
{
return x.Category.Equals(y.Category) &&
x.Country.Equals(y.Country);
}
public int GetHashCode(MyObj obj)
{
return obj.GetHashCode();
}
}
然后在这里使用
var comparer = new MyObjEqualityComparer();
myObjs.Where(m => m.SomeProperty == "whatever").Distinct(comparer);
你没有做错,这只是 .NET Framework 中
.Distinct()
的错误实现。
其他答案中已经显示了一种修复它的方法,但还有一种更短的解决方案可用,其优点是您可以在任何地方轻松地将其用作扩展方法,而无需调整对象的哈希值。
看看这个:
var myQuery=(from x in Customers select x).MyDistinct(d => d.CustomerID);
注意: 此示例使用数据库查询,但它也适用于可枚举对象列表。
MyDistinct
声明:
public static class Extensions
{
public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query,
Func<T, V> f)
{
return query.GroupBy(f).Select(x=>x.First());
}
}
或者如果你想要更短,这与上面相同,但作为“one-liner”:
public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query, Func<T, V> f)
=> query.GroupBy(f).Select(x => x.First());
它适用于一切事物,无论是物体还是实体。如果需要,您可以通过替换上面给出的示例中的返回类型和第一个参数类型来为
IQueryable<T>
创建第二个重载扩展方法。
测试数据: 你可以用这个测试数据来尝试一下:
List<A> GetData()
=> new List<A>()
{
new A() { X="1", Y="2" }, new A() { X="1", Y="2" },
new A() { X="2", Y="3" }, new A() { X="2", Y="3" },
new A() { X="1", Y="3" }, new A() { X="1", Y="3" },
};
class A
{
public string X;
public string Y;
}
示例:
void Main()
{
// returns duplicate rows:
GetData().Distinct().Dump();
// Gets distinct rows by i.X
GetData().MyDistinct(i => i.X).Dump();
}
有关解释,请查看其他答案。我只是提供一种方法来处理这个问题。
您可能会喜欢这个:
public class LambdaComparer<T>:IEqualityComparer<T>{
private readonly Func<T,T,bool> _comparer;
private readonly Func<T,int> _hash;
public LambdaComparer(Func<T,T,bool> comparer):
this(comparer,o=>0) {}
public LambdaComparer(Func<T,T,bool> comparer,Func<T,int> hash){
if(comparer==null) throw new ArgumentNullException("comparer");
if(hash==null) throw new ArgumentNullException("hash");
_comparer=comparer;
_hash=hash;
}
public bool Equals(T x,T y){
return _comparer(x,y);
}
public int GetHashCode(T obj){
return _hash(obj);
}
}
用途:
public void Foo{
public string Fizz{get;set;}
public BarEnum Bar{get;set;}
}
public enum BarEnum {One,Two,Three}
var lst=new List<Foo>();
lst.Distinct(new LambdaComparer<Foo>(
(x1,x2)=>x1.Fizz==x2.Fizz&&
x1.Bar==x2.Bar));
您甚至可以将其包裹起来以避免写出吵闹的
new LambdaComparer<T>(...)
事情:
public static class EnumerableExtensions{
public static IEnumerable<T> SmartDistinct<T>
(this IEnumerable<T> lst, Func<T, T, bool> pred){
return lst.Distinct(new LambdaComparer<T>(pred));
}
}
用途:
lst.SmartDistinct((x1,x2)=>x1.Fizz==x2.Fizz&&x1.Bar==x2.Bar);
注意:仅适用于 Linq2Objects
我知道这是一个老问题,但我对任何答案都不满意。我花了一些时间自己解决这个问题,我想分享我的发现。
首先阅读并理解这两件事很重要:
长话短说,为了让
.Distinct()
扩展了解如何确定对象的相等性 - 您必须为对象 T 定义一个“EqualityComparer”。当您阅读 Microsoft 文档时,它的字面意思是:
我们建议您从 EqualityComparer 类派生 而不是实现 IEqualityComparer 接口...
这就是你决定使用什么的方式,因为它已经为你决定了。
为了使
.Distinct()
扩展成功运行,您必须确保可以准确比较您的对象。对于 .Distinct()
来说,GetHashCode()
方法才是真正重要的。
您可以通过编写一个
GetHashCode()
实现来亲自测试这一点,该实现仅返回传入对象的当前哈希码,您将看到结果很糟糕,因为该值在每次运行时都会发生变化。这使得您的对象过于独特,这就是为什么实际编写此方法的正确实现很重要。
下面是来自
IEqualityComparer<T>
页面的代码示例的精确副本,其中包含测试数据,对 GetHashCode()
方法进行了小修改,并通过注释来演示这一点。
//Did this in LinqPad
void Main()
{
var lst = new List<Box>
{
new Box(1, 1, 1),
new Box(1, 1, 1),
new Box(1, 1, 1),
new Box(1, 1, 1),
new Box(1, 1, 1)
};
//Demonstration that the hash code for each object is fairly
//random and won't help you for getting a distinct list
lst.ForEach(x => Console.WriteLine(x.GetHashCode()));
//Demonstration that if your EqualityComparer is setup correctly
//then you will get a distinct list
lst = lst
.Distinct(new BoxEqualityComparer())
.ToList();
lst.Dump();
}
public class Box
{
public Box(int h, int l, int w)
{
this.Height = h;
this.Length = l;
this.Width = w;
}
public int Height { get; set; }
public int Length { get; set; }
public int Width { get; set; }
public override String ToString()
{
return String.Format("({0}, {1}, {2})", Height, Length, Width);
}
}
public class BoxEqualityComparer
: EqualityComparer<Box>
{
public override bool Equals(Box b1, Box b2)
{
if (b2 == null && b1 == null)
return true;
else if (b1 == null || b2 == null)
return false;
else if (b1.Height == b2.Height && b1.Length == b2.Length
&& b1.Width == b2.Width)
return true;
else
return false;
}
public override int GetHashCode(Box bx)
{
#region This works
//In this example each component of the box object are being XOR'd together
int hCode = bx.Height ^ bx.Length ^ bx.Width;
//The hashcode of an integer, is that same integer
return hCode.GetHashCode();
#endregion
#region This won't work
//Comment the above lines and uncomment this line below if you want to see Distinct() not work
//return bx.GetHashCode();
#endregion
}
}