Using Distinct with LINQ and Objects

后端 未结 5 1394
余生分开走
余生分开走 2021-01-03 23:20

Until recently, I was using a Distinct in LINQ to select a distinct category (an enum) from a table. This was working fine.

I now need to have it distinct on a class

5条回答
  •  一整个雨季
    2021-01-03 23:31

    I know this is an old question, but I am not satisfied with any of the answers. I took time to figure this out for myself and I wanted to share my findings.

    First it is important to read and understand these two things:

    1. IEqualityComparer
    2. EqualityComparer

    Long story short in order to make the .Distinct() extension understand how to determine equality of your object - you must define a "EqualityComparer" for your object T. When you read the Microsoft docs it literally states:

    We recommend that you derive from the EqualityComparer class instead of implementing the IEqualityComparer interface...

    That is how you determine what to use, because it had been decided for you already.

    For the .Distinct() extension to work successfully you must ensure that your objects can be compared accurately. In the case of .Distinct() the GetHashCode() method is what really matters.

    You can test this out for yourself by writing a GetHashCode() implementation that just returns the current Hash Code of the object being passed in and you will see the results are bad because this value changes on each run. That makes your objects too unique which is why it is important to actually write a proper implementation of this method.

    Below is an exact copy of the code sample from IEqualityComparer's page with test data, small modification to the GetHashCode() method and comments to demonstrate the point.

    //Did this in LinqPad
    void Main()
    {
        var lst = new List
        {
            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
    {
        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
        }
    }
    

提交回复
热议问题