161

So I have a collection of objects. The exact type isn't important. From it I want to extract all the unique pairs of a pair of particular properties, thusly:

myObjectCollection.Select(item=>new
                                {
                                     Alpha = item.propOne,
                                     Bravo = item.propTwo
                                }
                 ).Distinct();

So my question is: Will Distinct in this case use the default object equals (which will be useless to me, since each object is new) or can it be told to do a different equals (in this case, equal values of Alpha and Bravo => equal instances)? Is there any way to achieve that result, if this doesn't do it?

GWLlosa
  • 23,995
  • 17
  • 79
  • 116
  • Is this LINQ-to-Objects or LINQ-to-SQL? If just objects, you're probably out of luck. However, if L2S, then it may work, as the DISTINCT would be passed onto the SQL statement. – James Curran Feb 12 '09 at 21:54
  • [the solution to your problem looks like this](https://stackoverflow.com/a/60412781/10391572) – Paul Alexeev Feb 26 '20 at 11:46

8 Answers8

206

Have a read through K. Scott Allen's excellent post here:

And Equality for All ... Anonymous Types

The short answer (and I quote):

Turns out the C# compiler overrides Equals and GetHashCode for anonymous types. The implementation of the two overridden methods uses all the public properties on the type to compute an object's hash code and test for equality. If two objects of the same anonymous type have all the same values for their properties – the objects are equal.

So it's totally safe to use the Distinct() method on a query that returns anonymous types.

Matt Hamilton
  • 200,371
  • 61
  • 386
  • 320
  • 3
    This is only true, I think, if the properties themselves are value types or implement value equality -- see my answer. – tvanfosson Feb 12 '09 at 22:09
  • Yes, since it uses GetHashCode on each property then it would only work if each property had its own unique implementation of that. I think most use-cases would only involve simple types as properties so it's generally safe. – Matt Hamilton Feb 12 '09 at 22:23
  • 4
    It winds up meaning that the equality of two of the anonymous types depends on the equality of the members, which is fine by me, since the members are defined someplace I can get at and override equality if I have to. I just didn't want to have to create a class for this just to override equals. – GWLlosa Feb 12 '09 at 22:24
  • 4
    It might be worth petitioning MS to introduce the "key" syntax into C# that VB has (where you can specify certain properties of an anonymous type to be the 'primary key' - see the blog post I linked to). – Matt Hamilton Feb 12 '09 at 22:32
  • @tvanfosson I did a quick test with class objects assigned to properties of the anonymous type, and it seems to fall back to object reference equality for those properties, as I expected. – angularsen Sep 25 '16 at 12:44
14
public class DelegateComparer<T> : IEqualityComparer<T>
{
    private Func<T, T, bool> _equals;
    private Func<T, int> _hashCode;
    public DelegateComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        _equals= equals;
        _hashCode = hashCode;
    }
    public bool Equals(T x, T y)
    {
        return _equals(x, y);
    }

    public int GetHashCode(T obj)
    {
        if(_hashCode!=null)
            return _hashCode(obj);
        return obj.GetHashCode();
    }       
}

public static class Extensions
{
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items, 
        Func<T, T, bool> equals, Func<T,int> hashCode)
    {
        return items.Distinct(new DelegateComparer<T>(equals, hashCode));    
    }
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items,
        Func<T, T, bool> equals)
    {
        return items.Distinct(new DelegateComparer<T>(equals,null));
    }
}

var uniqueItems=students.Select(s=> new {FirstName=s.FirstName, LastName=s.LastName})
            .Distinct((a,b) => a.FirstName==b.FirstName, c => c.FirstName.GetHashCode()).ToList();

Sorry for the messed up formatting earlier

  • This Extensions cannot handle of type `object` and `object`. If the both `object` is `string` it still return the duplicate rows. Try the `FirstName` is typeof `object` and assign with the same `string` there. – CallMeLaNN May 26 '11 at 05:50
  • This is a great answer for typed objects but not needed for anonymous types. – crokusek Mar 24 '17 at 23:38
5

Interesting that it works in C# but not in VB

Returns the 26 letters:

var MyBet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ";
MyBet.ToCharArray()
.Select(x => new {lower = x.ToString().ToLower(), upper = x.ToString().ToUpper()})
.Distinct()
.Dump();

Returns 52...

Dim MyBet = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ"
MyBet.ToCharArray() _
.Select(Function(x) New With {.lower = x.ToString.ToLower(), .upper = x.ToString.ToUpper()}) _
.Distinct() _
.Dump()
GeorgeBarker
  • 991
  • 1
  • 11
  • 7
  • 13
    If you add the `Key` keyword to the anonymous type the `.Distinct()` will work as intended (e.g. `New With { Key .lower = x.ToString.ToLower(), Key .upper = x.ToString.ToUpper()}`). – Cᴏʀʏ Apr 11 '11 at 20:57
  • 3
    Cory is right. The correct translation of the C# code `new {A = b}` is `New {Key .A = b}`. Non-key properties in anonymous VB classes are mutable, which is why they are compared by reference. In C#, all properties of anonymous classes are immutable. – Heinzi Nov 18 '11 at 10:52
4

I ran a little test and found that if the properties are value types, it seems to work ok. If they are not value types, then the type needs provide it's own Equals and GetHashCode implementations for it to work. Strings, I would think, would work.

tvanfosson
  • 524,688
  • 99
  • 697
  • 795
2

You can create your own Distinct Extension method which takes lambda expression. Here's an example

Create a class which derives from IEqualityComparer interface

public class DelegateComparer<T> : IEqualityComparer<T>
{
    private Func<T, T, bool> _equals;
    private Func<T, int> _hashCode;
    public DelegateComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        _equals= equals;
        _hashCode = hashCode;
    }
    public bool Equals(T x, T y)
    {
        return _equals(x, y);
    }

    public int GetHashCode(T obj)
    {
        if(_hashCode!=null)
            return _hashCode(obj);
        return obj.GetHashCode();
    }       
}

Then create your Distinct Extension method

public static class Extensions
{
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items, 
        Func<T, T, bool> equals, Func<T,int> hashCode)
    {
        return items.Distinct(new DelegateComparer<T>(equals, hashCode));    
    }
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> items,
        Func<T, T, bool> equals)
    {
        return items.Distinct(new DelegateComparer<T>(equals,null));
    }
}

and you can use this method find distinct items

var uniqueItems=students.Select(s=> new {FirstName=s.FirstName, LastName=s.LastName})
            .Distinct((a,b) => a.FirstName==b.FirstName, c => c.FirstName.GetHashCode()).ToList();
Buildstarted
  • 26,529
  • 10
  • 84
  • 95
  • This Extensions cannot handle of type `object` and `object`. If the both `object` is `string` it still return the duplicate rows. Try the `FirstName` is typeof `object` and assign with the same `string` there. – CallMeLaNN May 26 '11 at 05:54
0

In order for it to work in VB.NET, you need to specify the Key keyword before every property in the anonymous type, just like this:

myObjectCollection.Select(Function(item) New With
{
    Key .Alpha = item.propOne,
    Key .Bravo = item.propTwo
}).Distinct()

I was struggling with this, I thought VB.NET didn't support this type of feature, but actually it does.

Alisson Reinaldo Silva
  • 10,009
  • 5
  • 65
  • 83
0

If Alpha and Bravo both inherit from a common class, you will be able to dictate the equality check in the parent class by implementing IEquatable<T>.

For example:

public class CommonClass : IEquatable<CommonClass>
{
    // needed for Distinct()
    public override int GetHashCode() 
    {
        return base.GetHashCode();
    }

    public bool Equals(CommonClass other)
    {
        if (other == null) return false;
        return [equality test];
    }
}
ern
  • 1,522
  • 13
  • 19
  • so if you use as properties of your anonymous types classes that implements IEquatable, Equals is called instead of the default behavior (check of all public properties via reflection?) – D_Guidi Jan 14 '11 at 10:17
0

Hey there i got the same problem and i found an solution. You have to implement the IEquatable interface or simply override the (Equals & GetHashCode) Methods. But this is not the trick, the trick coming in the GetHashCode Method. You should not return the hash code of the object of your class but you should return the hash of the property you want to compare like that.

public override bool Equals(object obj)
    {
        Person p = obj as Person;
        if ( obj == null )
            return false;
        if ( object.ReferenceEquals( p , this ) )
            return true;
        if ( p.Age == this.Age && p.Name == this.Name && p.IsEgyptian == this.IsEgyptian )
            return true;
        return false;
        //return base.Equals( obj );
    }
    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }

As you see i got an class called person got 3 properties (Name,Age,IsEgyptian"Because I am") In the GetHashCode i returned the hash of the Name property not the Person object.

Try it and it will work ISA. Thank you, Modather Sadik

  • 1
    GetHashCode should use all of the same fields and properties that are used in the comparison for equality, not just one of them. i.e. `public override int GetHashCode() { return this.Name.GetHashCode() ^ this.Age.GetHashCode() ^ this.IsEgyptian.GetHashCode(); }` – JG in SD Dec 19 '12 at 23:03
  • For information on generating a good hash algorithm: http://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode – JG in SD Dec 19 '12 at 23:15