直到最近,我在LINQ中使用Distinct从表中选择一个不同的类别(枚举)。这很好。
我现在需要在包含类别和国家(两个枚举)的类中区分它。 The Distinct现在不起作用。
我做错了什么?
答案 0 :(得分:19)
我相信这篇文章解释了你的问题: 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());
答案 1 :(得分:4)
尝试使用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);
答案 2 :(得分:3)
有关解释,请查看其他答案。我只是提供一种方法来处理这个问题。
您可能希望this:
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
可靠地工作答案 3 :(得分:2)
您没有做错,这只是.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());
}
}
它适用于一切,对象和实体。如果需要,您可以通过替换上面给出的示例中的返回类型和第一个参数类型,为IQueryable<T>
创建第二个重载扩展方法。
答案 4 :(得分:0)
我知道这是一个老问题,但是我对任何答案都不满意。我花了一些时间自己解决这个问题,我想分享我的发现。
首先,重要的是要阅读和理解这两件事:
长话短说,是为了使.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
}
}