views:

12016

answers:

8

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?

A: 

Test it out and see if it works as you want it to!

Justice
I'm not at/near my compiler at the moment, and figured that either way, the answer here might be useful to others (google doesn't have an obvious answer, either).
GWLlosa
A: 

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
+25  A: 

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
This is only true, I think, if the properties themselves are value types or implement value equality -- see my answer.
tvanfosson
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
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
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
Very interesting article. Thanks!
Alexander Prokofyev
A: 

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  A: 

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
A: 

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 : IEqualityComparer { private Func _equals; private Func _hashCode; public DelegateComparer(Func equals, Func 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 Distinct(this IEnumerable items, Func equals, Func hashCode) { return items.Distinct(new DelegateComparer(equals, hashCode));
} public static IEnumerable Distinct(this IEnumerable items, Func equals) { return items.Distinct(new DelegateComparer(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();

+6  A: 
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

+2  A: 

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