Expression Equality Comparer

by justin 1. April 2009 02:24

I was just encountering an issue where I needed to get the distinct elements of two collections but not necessarily distinct instances. When trying to resolve this issue I found the convenient Linq method “Distinct”. By default it will return to you distinct instances but if you want to evaulate equality based on some other qualification then you’re stuck with creating a custom IEqualityComparer<T>. There may be something in the framework already that solves this problem but I couldn’t find it. So I created a really simple Expression based equality comparer and a corresponding enumerable extension method.

public static class Enumerable
{
    public static IEnumerable<T> Distinct<T>(
        this IEnumerable<T> items, 
        Expression<Func<T, T, bool>> equalityComparer)
    {
        ExpressionEqualityComparer<T> comparer = 
            new ExpressionEqualityComparer<T>(equalityComparer);
        return items.Distinct(comparer);
    }
}

public class ExpressionEqualityComparer<T> : EqualityComparer<T>
{
    private Func<T, T, bool> equals;
    public ExpressionEqualityComparer(
        Expression<Func<T, T, bool>> equals)
    {
        this.equals = equals.Compile();
    }

    public override bool Equals(T x, T y)
    {
        return equals(x, y);
    }

    public override int GetHashCode(T obj)
    {
        return base.GetHashCode();
    }
}

This allows you to call Distinct with an expression as a parameter which is used to evaluate equality rather than requiring you to create a custom comparer object every time. It calls its own GetHashCode() instead of the objects in order to ensure that different instances will still evaluate through the equality comparer. Here is an example of the usage.

Example[] c1 = new Example[] { 
    new Example { Id = 1, Name = "one" }, 
    new Example { Id = 2, Name = "two" } };
Example[] c2 = new Example[] { 
    new Example { Id = 2, Name = "two" }, 
    new Example { Id = 3, Name = "three" } };

var c3 = c1.Concat(c2).Distinct((e1, e2) => e1.Name == e2.Name);

Tags: ,

C# | .NET

Comments

4/10/2009 8:40:34 AM #

I really like this idea, but I'm curious why you need to use an Expression. Wouldn't it work the same (without an expensive Compile) if the constructor just took a Func<T, T, bool> delegate?

Cheers ~
Keith

Keith Dahlby |

4/10/2009 9:48:01 AM #

It would except that, in this case, I wanted to limit the types of code that can be done in this comparer specifically to an expression. If you do just Func<T, T, bool> then the you can also put statements in your lambda, which seemed innapropriate.

I'm not convinced that the Compile is that expensive either, but I haven't tested it out thoroughly.

Justin Chase |

Comments are closed

About Me

sweetest hat ever

I'm a software developer from Minnesota and this blog largely focuses on various technical concepts I am thinking about at the moment. I currently work for Microsoft in the St. Paul office of the Expression product group.

RecentPosts