5

I am writing this neat class implementing IEqualityComparer, so that I can just pass any anonymous type to it (or actually any type with properties) and it will automatically compare the types by comparing the property values of the types.

public class CompareProperty<T> : IEqualityComparer<T>
    {
        private Type type;
        private PropertyInfo propInfo;
        private string _fieldName;

        public string fieldName
        {
            get;
            set;
        }

        public CompareProperty(string fieldName)
        {
            this.fieldName = fieldName;
        }

        public bool Equals<T>(T x, T y)
        {
            if (this.type == null)
            {
                type = x.GetType();
                propInfo = type.GetProperty(fieldName);
            }
            object objX = propInfo.GetValue(x, null);
            object objY = propInfo.GetValue(y, null);
            return objX.ToString() == objY.ToString();
        }
    }

I thought this was a nice little helper function I could use many times.

In order to use this, I have to do:

var t = typeof(CompareProperty<>);
var g = t.MakeGenericType(infoType.GetType());
var c = g.GetConstructor(new Type[] {String.Empty.GetType()});
var obj = c.Invoke(new object[] {"somePropertyName"});

Fair enough, but what do I do with the obj variable it returns?

someEnumerable.Distinct(obj);

The overload of the distinct extension function does not accept this, because it does not see a IEqualityComparer type, it only sees an object, of course.

someEnumerable.Distinct((t) obj);
someEnumerable.Distinct(obj as t);

This also doesn't work. Type/Namespace not found (red underline).

How do I get this straight?

Nikola Anusev
  • 6,940
  • 1
  • 30
  • 46
cdbeelala89
  • 2,066
  • 3
  • 28
  • 39
  • 3
    Just FYI, the default comparer for anonymous type *already* compares based on property values. – Adam Houldsworth Jul 05 '12 at 13:57
  • 1
    What's the type of `someEnumerable`? It's tough to see how you're going to get this to compile without knowing `T`, at which point half of the reflection goes out of the window... – Jon Skeet Jul 05 '12 at 13:58
  • 1
    I don't think its sensible to try and implement this generically. What are you adding? – Jodrell Jul 05 '12 at 14:06
  • someEnumerable is of type IEnumerable. infoType is a simple anonymous type with some properties. Hm what default comparer for anonymous types do you mean? What is it called? – cdbeelala89 Jul 05 '12 at 14:16
  • `return objX.ToString() == objY.ToString();` Not smart. Make an equality function for all primitives, then, recursively go into types until you reach primitives. – Mark Segal Jul 05 '12 at 14:16
  • OK, i still cannot figure it out. Say I have a list of a anonymous type {name = "bla", name2 = "blub"}: How do I do a distinct() on the list ONLY by name2 (ignoring name)? – cdbeelala89 Jul 05 '12 at 16:12
  • @cdbeelala89 do you really want it to be based on "string name" of the property? Otherwise you want to see this: [wrap-a-delegate-in-an-iequalitycomparer?lq=1](http://stackoverflow.com/questions/98033/wrap-a-delegate-in-an-iequalitycomparer?lq=1) – nawfal Oct 23 '13 at 10:40

2 Answers2

7

I'll first provide a solution for non-anonymous types and afterwards extend it to work for anonymous types as well. Hopefully, it will help you to understand what people were trying to say in comments to your question.

My "universal" IEqualityComparer<> looks like this:

public class PropertyComparer<T> : IEqualityComparer<T>
{
    private readonly PropertyInfo propertyToCompare;

    public PropertyComparer(string propertyName)
    {
        propertyToCompare = typeof(T).GetProperty(propertyName);
    }
    public bool Equals(T x, T y)
    {
        object xValue = propertyToCompare.GetValue(x, null);
        object yValue = propertyToCompare.GetValue(y, null);
        return xValue.Equals(yValue);
    }

    public int GetHashCode(T obj)
    {
        object objValue = propertyToCompare.GetValue(obj, null);
        return objValue.GetHashCode();
    }
}

Say that we want to use it with non-anonymous type first, like Person:

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string Surname { get; set; }
 }

The usage would be quite straightforward:

IEnumerable<Person> people = ... ; // some database call here
var distinctPeople = people.Distinct(new PropertyComparer<Person>("FirstName"));

As you can see, to use the PropertyComparer<T>, we need to specify the type (the T) instances of which are going to be compared against each other. What would the T be when dealing with anonymous types? Since they are generated at runtime, you cannot use the comparer by directly creating its instance, simply because you do not know the T at compile time. Instead, you need to use type-inference to let the C# compiler infer T from context on its own. Neat way to do this is to write the following extension method:

public static class LinqExtensions
{
    public static IEnumerable<T> WithDistinctProperty<T>(this IEnumerable<T> source, string propertyName)
    {
        return source.Distinct(new PropertyComparer<T>(propertyName));
    }
}

Now it will also work with anonymous types:

var distinctPeople = people
        .Select(x => new { x.FirstName, x.Surname })
        .WithDistinctProperty("FirstName");

All of a sudden, there is no need to specify the exact type the query is dealing with anywhere, since C# compiler is smart enough to infer it from the context (which, in this case, is being provided from the type of source parameter in the extension method).

Hope this will help you.

Nikola Anusev
  • 6,940
  • 1
  • 30
  • 46
  • 4
    It is generic solution, but it's slow because of reflection and has hard coded names of properties ("FirstName"). I don't this would fly in a production environment. – tymtam Nov 15 '12 at 00:53
  • @Tymek "Slow" is a relative term. If you have another solution in mind that is more performant and still satisfies OP's needs, I would be most interested to see it. Regarding hard-coded property names - that could be solved by using something like [this](http://stackoverflow.com/questions/3269518/get-the-property-name-used-in-a-lambda-expression-in-net-3-5), but I didn't want to complicate the matters further. – Nikola Anusev Nov 15 '12 at 11:14
  • How about having a `UniversalEqualityAdapter : IEqualityComparer` whose constructor takes a `Func` and optionally an `IEqualityComparer` (or else uses `EqualityComparer.Default`? Then to make an EqualityComparer that tests `Point` instances for the same X coordinate, one would create `new UniversalEqualityComparer((Point pt) => pt.x)`. That should avoid the Reflection overhead, while being more versatile as well. – supercat May 15 '13 at 22:50
  • @supercat Yeah, but for each `T`, you would have to manually specify a comparison `Func`. Of course, this is much better than having to create a brand new `IEqualityComparer` implementation, but still - it's not automatic. – Nikola Anusev May 16 '13 at 18:40
  • @NikolaAnusev: It wouldn't be a comparison func; it would be a function that converts the object to some other comparable type. The function required to extract the value of a property from an instance is a little longer than a quote-delineated function name, but not by much. – supercat May 16 '13 at 18:45
  • This really helped me out. I added another function to this class so that it could compare more than 1 field in my anonymous class. Thanks – brasewel Oct 23 '14 at 20:48
2

Just add own validations.

class PropertyComparer<T, TResult> : IEqualityComparer<T>
{
    private Func<T, TResult> _getProperty;

    public PropertyComparer(Func<T, TResult> predicate)
    {
        this._getProperty = predicate;
    }

    public bool Equals(T x, T y)
    {
        return this._getProperty(x).Equals(_getProperty(y));
    }

    public int GetHashCode(T obj)
    {
        return this._getProperty(obj).GetHashCode();
    }
}

Class with extension method:

public static class IEnumerableExtensions
{
    public static IEnumerable<TSource> DistinctBy<TSource, TResult>
        (this IEnumerable<TSource> source, Func<TSource, TResult> predicate)
    {
        return source.Distinct(new PropertyComparer<TSource, TResult>(predicate));
    }
}

Usage:

someEnumerable.DistinctBy(i => i.SomeProperty);