29

I have these data transfer objects:

public class Report 
{
    public int Id { get; set; }
    public int ProjectId { get; set; }
    //and so on for many, many properties.
}

I don't want to write

public bool areEqual(Report a, Report b)
{
    if (a.Id != b.Id) return false;
    if (a.ProjectId != b.ProjectId) return false;
    //Repeat ad nauseum
    return true;
}

Is there a faster way to test if two object with only properties have the same values (something that doesn't require one line of code or one logical expression per property?)

Switching to structs is not an option.

Gabriel Morin
  • 667
  • 8
  • 13
MatthewMartin
  • 32,326
  • 33
  • 105
  • 164
  • I was thinking about this. In my mind the best way to do this would be via an IDE tool. It looks like Eclipse has one- http://www.eclipsezone.com/eclipse/forums/t92613.rhtml. I Wonder if there is something along those lines for VS.NET? – RichardOD Jun 12 '09 at 14:03
  • @RichardOD: ReSharper can do this in VS.NET for instance. – Lucero Dec 02 '09 at 10:16

5 Answers5

68

How about some reflection, perhaps using Expression.Compile() for performance? (note the static ctor here ensures we only compile it once per T):

using System;
using System.Linq.Expressions;

public class Report {
    public int Id { get; set; }
    public int ProjectId { get; set; }
    static void Main() {
        Report a = new Report { Id = 1, ProjectId = 13 },
            b = new Report { Id = 1, ProjectId = 13 },
            c = new Report { Id = 1, ProjectId = 12 };
        Console.WriteLine(PropertyCompare.Equal(a, b));
        Console.WriteLine(PropertyCompare.Equal(a, c));
    }
}
static class PropertyCompare {
    public static bool Equal<T>(T x, T y) {
        return Cache<T>.Compare(x, y);
    }
    static class Cache<T> {
        internal static readonly Func<T, T, bool> Compare;
        static Cache() {
            var props = typeof(T).GetProperties();
            if (props.Length == 0) {
                Compare = delegate { return true; };
                return;
            }
            var x = Expression.Parameter(typeof(T), "x");
            var y = Expression.Parameter(typeof(T), "y");

            Expression body = null;
            for (int i = 0; i < props.Length; i++) {
                var propEqual = Expression.Equal(
                    Expression.Property(x, props[i]),
                    Expression.Property(y, props[i]));
                if (body == null) {
                    body = propEqual;
                } else {
                    body = Expression.AndAlso(body, propEqual);
                }
            }
            Compare = Expression.Lambda<Func<T, T, bool>>(body, x, y)
                          .Compile();
        }
    }
}

Edit: updated to handle fields too:

static class MemberCompare
{
    public static bool Equal<T>(T x, T y)
    {
        return Cache<T>.Compare(x, y);
    }
    static class Cache<T>
    {
        internal static readonly Func<T, T, bool> Compare;
        static Cache()
        {
            var members = typeof(T).GetProperties(
                BindingFlags.Instance | BindingFlags.Public)
                .Cast<MemberInfo>().Concat(typeof(T).GetFields(
                BindingFlags.Instance | BindingFlags.Public)
                .Cast<MemberInfo>());
            var x = Expression.Parameter(typeof(T), "x");
            var y = Expression.Parameter(typeof(T), "y");

            Expression body = null;
            foreach(var member in members)
            {
                Expression memberEqual;
                switch (member.MemberType)
                {
                    case MemberTypes.Field:
                        memberEqual = Expression.Equal(
                            Expression.Field(x, (FieldInfo)member),
                            Expression.Field(y, (FieldInfo)member));
                        break;
                    case MemberTypes.Property:
                        memberEqual = Expression.Equal(
                            Expression.Property(x, (PropertyInfo)member),
                            Expression.Property(y, (PropertyInfo)member));
                        break;
                    default:
                        throw new NotSupportedException(
                            member.MemberType.ToString());
                }
                if (body == null)
                {
                    body = memberEqual;
                }
                else
                {
                    body = Expression.AndAlso(body, memberEqual);
                }
            }
            if (body == null)
            {
                Compare = delegate { return true; };
            }
            else
            {
                Compare = Expression.Lambda<Func<T, T, bool>>(body, x, y)
                              .Compile();
            }
        }
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 2
    Wow, that's really sweet. Much nicer that the pure reflection version. – Jonathan Allen Jun 12 '09 at 18:17
  • 1
    Why you not initialize body with Expression.Constant(true) to avoid if in cycle? – ASpirin Dec 09 '10 at 20:11
  • @ASpirin - the `if` is only when **creating** the `Expression`, which is something you do once and then cache/re-use. I'd rather do that than add something else to the expression.... – Marc Gravell Dec 09 '10 at 21:31
  • 1
    +1. Code generation through expressions is immensely powerful but underused in my opinion. – JulianR Nov 21 '12 at 10:17
  • The first version of this code should read typeof(T).GetProperties( BindingFlags.Instance | BindingFlags.Public) instead of typeof(T).GetProperties() – Jason Kealey Mar 05 '13 at 13:39
  • @JasonKealey I dont think you require it. `GetProperties` by default has the same flags you mentioned I believe. In fact its not required in second version too (unless you want a different behaviour than `BindingFlags.Instance | BindingFlags.Public` – nawfal May 06 '13 at 20:03
  • If I am not wrong, that code only compares members on the "first level". I.e. if you have a class with members which are of a complex type (and not a simple type), those members would be compared using their `Equals()` method, if they have one. I guess for that case, your code would have to be extended to run recursively? – Tobias Oct 23 '18 at 16:07
  • @Tobias yes; you also have problems with lists, for example - out of order, missing, extra, etc – Marc Gravell Oct 23 '18 at 20:24
6

Originally answered at (question 1831747)

Check out my MemberwiseEqualityComparer to see if it fits your needs.

It's really easy to use and quite efficient too. It uses IL-emit to generate the entire Equals and GetHashCode function on the first run (once for each type used). It will compare each field (private or public) of the given object using the default equality comparer for that type (EqualityComparer.Default). We've been using it in production for a while and it seems stable but I'll leave no guarantees =)

It takes care of all those pescy edge-cases that you rarely think of when you're rolling your own equals method (ie, you can't comparer your own object with null unless you've boxed it in an object first and lot's off more null-related issues).

I've been meaning to write a blog post about it but haven't gotten around to it yet. The code is a bit undocumented but if you like it I could clean it up a bit.

public override int GetHashCode()
{
    return MemberwiseEqualityComparer<Foo>.Default.GetHashCode(this);
}

public override bool Equals(object obj)
{
    if (obj == null)
        return false;

    return Equals(obj as Foo);
}

public override bool Equals(Foo other)
{
    return MemberwiseEqualityComparer<Foo>.Default.Equals(this, other);
}

The MemberwiseEqualityComparer is released under the MIT license meaining you can do pretty much whatever you want with it, including using it in proprietary solutions without changing you licensing a bit.

Community
  • 1
  • 1
Markus Olsson
  • 22,402
  • 9
  • 55
  • 62
  • 1
    One possible enhancement would be to have the equality-test generator allow the use of field attributes to indicate which fields encapsulate *identity* and which ones encapsulate *value*. A fairly common pattern is to encapsulate a mutable-class value by holding a reference that will never be exposed to anything that might mutate it. Such a field should be tested for value equality even though calling `Equals` on its type would test for reference equality. – supercat May 14 '13 at 19:14
4

I've extended Marc's code to be a fully-fledged IEqualityComparer implementation for my own uses, and thought this may be useful to others in the future:

/// <summary>
/// An <see cref="IEqualityComparer{T}"/> that compares the values of each public property.
/// </summary>
/// <typeparam name="T"> The type to compare. </typeparam>
public class PropertyEqualityComparer<T> : IEqualityComparer<T>
{
    // http://stackoverflow.com/questions/986572/hows-to-quick-check-if-data-transfer-two-objects-have-equal-properties-in-c/986617#986617

    static class EqualityCache
    {
        internal static readonly Func<T, T, bool> Compare;
        static EqualityCache()
        {
            var props = typeof(T).GetProperties();
            if (props.Length == 0)
            {
                Compare = delegate { return true; };
                return;
            }
            var x = Expression.Parameter(typeof(T), "x");
            var y = Expression.Parameter(typeof(T), "y");

            Expression body = null;
            for (int i = 0; i < props.Length; i++)
            {
                var propEqual = Expression.Equal(
                    Expression.Property(x, props[i]),
                    Expression.Property(y, props[i]));
                if (body == null)
                {
                    body = propEqual;
                }
                else
                {
                    body = Expression.AndAlso(body, propEqual);
                }
            }
            Compare = Expression.Lambda<Func<T, T, bool>>(body, x, y).Compile();
        }
    }

    /// <inheritdoc/>
    public bool Equals(T x, T y)
    {
        return EqualityCache.Compare(x, y);
    }

    static class HashCodeCache
    {
        internal static readonly Func<T, int> Hasher;
        static HashCodeCache()
        {
            var props = typeof(T).GetProperties();
            if (props.Length == 0)
            {
                Hasher = delegate { return 0; };
                return;
            }
            var x = Expression.Parameter(typeof(T), "x");

            Expression body = null;
            for (int i = 0; i < props.Length; i++)
            {
                var prop = Expression.Property(x, props[i]);
                var type = props[i].PropertyType;
                var isNull = type.IsValueType ? (Expression)Expression.Constant(false, typeof(bool)) : Expression.Equal(prop, Expression.Constant(null, type));
                var hashCodeFunc = type.GetMethod("GetHashCode", BindingFlags.Instance | BindingFlags.Public);
                var getHashCode = Expression.Call(prop, hashCodeFunc);
                var hashCode = Expression.Condition(isNull, Expression.Constant(0, typeof(int)), getHashCode);

                if (body == null)
                {
                    body = hashCode;
                }
                else
                {
                    body = Expression.ExclusiveOr(Expression.Multiply(body, Expression.Constant(typeof(T).AssemblyQualifiedName.GetHashCode(), typeof(int))), hashCode);
                }
            }
            Hasher = Expression.Lambda<Func<T, int>>(body, x).Compile();
        }
    }

    /// <inheritdoc/>
    public int GetHashCode(T obj)
    {
        return HashCodeCache.Hasher(obj);
    }
}
David Pfeffer
  • 38,869
  • 30
  • 127
  • 202
2

Unfortunately you are going to have to write the method to compare the field values. System.ValueType is built to use reflection and compare the field values of a struct but even this is unadvisable due to slow performance. The best thing to do is to override the Equals method and also implement the IEquatable<T> interface for a strongly typed Equals overload.

While you are at it, you might as well provide a good GetHashCode override as well to complement the Equals implementation. All of these steps are considered good practice.

Andrew Hare
  • 344,730
  • 71
  • 640
  • 635
1

You will need to use reflection to do this, please follow this link --> Comparing object properties in c#

Community
  • 1
  • 1
Bhaskar
  • 10,537
  • 6
  • 53
  • 64