使用与LINQ和对象不同的

时间:2010-12-14 11:31:48

标签: linq distinct

直到最近,我在LINQ中使用Distinct从表中选择一个不同的类别(枚举)。这很好。

我现在需要在包含类别和国家(两个枚举)的类中区分它。 The Distinct现在不起作用。

我做错了什么?

5 个答案:

答案 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)

我知道这是一个老问题,但是我对任何答案都不满意。我花了一些时间自己解决这个问题,我想分享我的发现。

首先,重要的是要阅读和理解这两件事:

  1. IEqualityComparer
  2. EqualityComparer

长话短说,是为了使.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
    }
}