-1

I am trying to write a method that compares the PROPERTIES of two instances of an object, with these two caveats:

  1. The objects will often have other complex objects as properties, or even arrays of other complex objects, and
  2. if a property is null on one instance of the object, don't worry if the other instance of the object has values or not.

Basically, something like this (see bottom of the code for desired results):

Car c1 = new Car{ make = "ford", model = "F-150",
                  options = new[]{ 
                             new Option {name = "seats", value = "leather"},
                             new Option {name = "radio", value = "XM"}}};

Car c2 = new Car{ make = "ford", model = "F-150"};

Car c3 = new Car{ make = "ford", model = "mustang", 
                  options = new[]{ 
                             new Option {name = "seats", value = "leather"},
                             new Option {name = "radio", value = "XM"}}};

Car c4 = new Car{ make = "ford", model = "F-150", 
                  options = new[]{ 
                             new Option {name = "seats", value = "leather"}}};

c1.MagicCompare(c2); // would return true, since all non-null properties match
c1.MagicCompare(c3); // would return false, since all non-null properties do NOT match
c1.MagicCompare(c4); // would return false, since the options array doesn't match

With these classes

public class Car
{
  public string make { get; set; }
  public string model { get; set; }
  public Option[] options { get; set;}

  public bool MagicCompare(Car obj)
  {
    // Do magic comparison
  }
}

public class Option
{
  public string name { get; set; }
  public string value { get; set; }
}
surfmuggle
  • 5,527
  • 7
  • 48
  • 77
kroe761
  • 3,296
  • 9
  • 52
  • 81
  • 2
    Do you want this to work specifically for this class, or use generics / reflection to work for this class and any inheriting from it? – Patrick Roberts May 04 '18 at 19:50
  • Absolutely want it to be generic enough to be used in multiple classes. Essentially, I am trying to check if a json response from a restful api call is correct. But I don't always care about the entire response, maybe just a small section of it. So I declare a class that matches the structure of the json as my expected class, give the properties some values (but maybe not all the properties) and then compare the non-null properties of the expected class to the corresponding properties on the json response. – kroe761 May 04 '18 at 19:58
  • It is not good practice to have `null` checks where they can be avoided. You could write a default constructor for `Options`. – Matthias Herrmann May 04 '18 at 20:05
  • You may try it in a non-generic way first in order to get an idea to do it youself, as there are no own attems so far currently. – MakePeaceGreatAgain May 04 '18 at 20:08
  • You should have commas not semi-colons in your init code - did you not test in VS or LINQPad? Your `Options` class is called `Option` everywhere else. Second `c1` should be `c3`. – NetMage May 04 '18 at 20:44
  • The question [Compare two objects' properties to find differences](https://stackoverflow.com/questions/957783/) might be relevant / helpfull. – surfmuggle Mar 01 '23 at 07:39

1 Answers1

0

There are lots of implementations of deep comparers for C# on the web.

This code implements one that handles a couple of field or property types, any that implement IEquatable or ICollection. You would need to add other types, as it is likely to blow up if you have e.g. a homegrown List type without implementing ICollection.

public static class Ext {
    public static bool HasDeclaredEquals(this object obj) =>
        typeof(IEquatable<>).MakeGenericType(obj.GetType())
                            .IsAssignableFrom(obj.GetType());
}

public class JSONCollectionComparer : IEqualityComparer {
    public new bool Equals(object x, object y) {
        var c = new JSONComparer();

        var xc = x as ICollection;
        var yc = y as ICollection;

        if (xc.Count != yc.Count)
            return false;

        var ans = true;
        foreach (var xyv in xc.Cast<object>().Zip(yc.Cast<object>(), (xv, yv) => (xv, yv))) {
            ans = ans && c.Equals(xyv.xv, xyv.yv);
            if (!ans)
                break;
        }

        return ans;
    }

    public int GetHashCode(object obj) {
        throw new NotImplementedException();
    }
}

public class JSONComparer : IEqualityComparer {
    public new bool Equals(object x, object y) {
        JSONComparer jc = null;
        JSONCollectionComparer jac = null;
        var ans = true;

        var members = x.GetType().GetMembers().Where(m => m is PropertyInfo || m is FieldInfo);
        foreach (var m in members) {
            var mType = m.GetMemberType();
            var xv = m.GetValue(x);
            var yv = m.GetValue(y);
            if (xv != null && yv != null)
                if (xv.HasDeclaredEquals())
                    ans = ans && xv.Equals(yv);
                else {
                    switch (xv) {
                        case ICollection xc:
                            jac = jac ?? new JSONCollectionComparer();
                            ans = ans && jac.Equals(xv, yv);
                            break;
                        default:
                            jc = jc ?? new JSONComparer();
                            ans = ans && jc.Equals(xv, yv); ;
                            break;
                    }
                }
            if (!ans)
                break;
        }

        return ans;
    }

    public int GetHashCode(object obj) {
        throw new NotImplementedException();
    }
}

Now your MagicCompare is easy:

public bool MagicCompare(Car obj) {
    // Do magic comparison
    return new JSONComparer().Equals(this, obj);
}
NetMage
  • 26,163
  • 3
  • 34
  • 55