By GWLlosa


2009-02-12 21:46:57 8 Comments

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?

8 comments

@Alisson 2018-07-13 20:24:30

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.

@Modather Sadik 2011-11-16 14:51:48

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

@JG in SD 2012-12-19 23:03:15

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 2012-12-19 23:15:02

For information on generating a good hash algorithm: stackoverflow.com/questions/263400/…

@Nabin Nepal 2009-03-27 00:35:19

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();

@CallMeLaNN 2011-05-26 05:54:40

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.

@GeorgeBarker 2009-10-26 20:58:11

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()

@Cᴏʀʏ 2011-04-11 20:57:26

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()}).

@Heinzi 2011-11-18 10:52:53

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.

@Nabin Nepal 2009-03-27 00:45:41

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

@CallMeLaNN 2011-05-26 05:50:25

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.

@crokusek 2017-03-24 23:38:02

This is a great answer for typed objects but not needed for anonymous types.

@tvanfosson 2009-02-12 22:07:45

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.

@ern 2009-02-12 22:00:16

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];
    }
}

@D_Guidi 2011-01-14 10:17:56

so if you use as properties of your anonymous types classes that implements IEquatable<T>, Equals is called instead of the default behavior (check of all public properties via reflection?)

@Matt Hamilton 2009-02-12 21:59:11

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.

@tvanfosson 2009-02-12 22:09:38

This is only true, I think, if the properties themselves are value types or implement value equality -- see my answer.

@Matt Hamilton 2009-02-12 22:23:11

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.

@GWLlosa 2009-02-12 22:24:24

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.

@Matt Hamilton 2009-02-12 22:32:45

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).

@Alexander Prokofyev 2009-04-23 12:46:41

Very interesting article. Thanks!

@angularsen 2016-09-25 12:44:52

@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.

Related Questions

Sponsored Content

15 Answered Questions

[SOLVED] Difference Between Select and SelectMany

  • 2009-06-06 03:54:16
  • Tarik
  • 474450 View
  • 996 Score
  • 15 Answer
  • Tags:   c# linq-to-sql linq

23 Answered Questions

[SOLVED] LINQ query on a DataTable

9 Answered Questions

[SOLVED] Can anonymous class implement interface?

  • 2008-10-10 12:20:32
  • Nick Randell
  • 153862 View
  • 444 Score
  • 9 Answer
  • Tags:   c# anonymous-types

20 Answered Questions

[SOLVED] LINQ's Distinct() on a particular property

13 Answered Questions

[SOLVED] When to use .First and when to use .FirstOrDefault with LINQ?

  • 2009-06-21 19:15:29
  • Metro Smurf
  • 540872 View
  • 798 Score
  • 13 Answer
  • Tags:   c# .net linq

7 Answered Questions

[SOLVED] Multiple "order by" in LINQ

  • 2008-11-18 13:34:11
  • Sasha
  • 578682 View
  • 1540 Score
  • 7 Answer
  • Tags:   linq sql-order-by

9 Answered Questions

[SOLVED] Group by in LINQ

  • 2011-09-06 19:44:20
  • test123
  • 1227378 View
  • 1008 Score
  • 9 Answer
  • Tags:   c# linq group-by

14 Answered Questions

[SOLVED] Type Checking: typeof, GetType, or is?

  • 2009-06-11 19:10:28
  • jasonh
  • 932092 View
  • 1438 Score
  • 14 Answer
  • Tags:   c#

17 Answered Questions

Sponsored Content