0

Suppose I have a class like below:

public class A
     {
        public string prop1 { get; set; }
        public List<B> prop2 { get; set; }  //B is a user-defined type
        // and so on
     }

Now suppose I have two objects of type A, a1 and a2. But not all of the properties of them are initialized. I need to check whether a1 has the same values for all the non-null properties of a2 (I don't know what is the term for this, maybe "sub-equality"?).

What I am trying to do is to overwrite Equals() method for all the types used in A, and then iterate through properties of a2 like this, and check the equality of the corresponding properties (if it is a List then I should use HashSet and SetEquals()).

Is there a better (more efficient, less code, etc) way?

Community
  • 1
  • 1
havij
  • 1,030
  • 14
  • 29
  • That answer uses reflection. In your case a more efficient approach would be to not use reflection because you know the objects and what properties they have at compile time. – CodingYoshi Apr 16 '18 at 03:42
  • Dupe to this https://stackoverflow.com/questions/8400028/comparing-two-instances-of-a-class ? – kurakura88 Apr 16 '18 at 03:45
  • Further questions. For a1 and a2 to be equal in your eyes, do they have to be the same list or is it enough that they contain equal elements? If the Lists can be different objects, do they need to be in the same order to be regarded as equal or is it sufficient for them to exist in both lists. You mention Hashset / SetEquals, but what if my A is List and I run a1.prop2 = new List { 1, 2, 3} and a2.prop2 = new List { 1, 2, 3, 2}? Are these equal? – Adam G Apr 16 '18 at 03:53
  • You could serialize both objects and compare the serialized versions, such as converting to JSON and compare the strings for equality. – Ron Beyer Apr 16 '18 at 03:56
  • @kurakura88 in my case, `a2` might have different set of non-null properties each time it is compared to some `a1` – havij Apr 16 '18 at 04:24
  • @AdamG for those properties that are `List`s, it suffices if the lists have objects that are 'equal' (i.e. order doesn't matter, and there won't be duplicates in the lists) – havij Apr 16 '18 at 04:27
  • @RonBeyer yeah I thought about that, but how can I serialize ONLY properties of `a1` that are non-nulll in `a2` ? – havij Apr 16 '18 at 04:30
  • @CodingYoshi what is the alternative? – havij Apr 16 '18 at 04:37

1 Answers1

1

There are several techniques, but here is something to start with. I have used an extension method which works all the way down to object (why not), but you could restrict it to higher levels of make it part of your base class itself.

Note that reflection is relatively expensive so if it is the sort of operation that you are doing often, you should consider caching the relevant PropertyInfos.

namespace ConsoleApp1
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Reflection;

    public class BaseClass<T>
    {
        public string prop1
        {
            get;
            set;
        }

        public List<T> prop2
        {
            get;
            set;
        } //B is a user-defined type
    }

    public class DescendentClass<T> : BaseClass<T>
    {
    }

    public static class DeepComparer
    {
        public static bool IsDeeplyEquivalent(this object current, object other)
        {
            if (current.Equals(other)) return true;
            Type currentType = current.GetType();
            // Assumption, cannot be equivalent if another class (descendent classes??)     
            if (currentType != other.GetType())
            {
                return false;
            }

            foreach (PropertyInfo propertyInfo in currentType.GetProperties())
            {
                object currentValue = propertyInfo.GetValue(current, null);
                object otherValue = propertyInfo.GetValue(other, null);
                // Assumption, nulls for a given property are considered equivalent
                if (currentValue == null && otherValue == null)
                {
                    continue;
                }

                // One is null, the other isn't so are not equal
                if (currentValue == null || otherValue == null)
                {
                    return false;
                }

                ICollection currentCollection = currentValue as ICollection;
                if (currentCollection == null)
                {
                    // Not a collection, just check equality
                    if (!currentValue.Equals(otherValue))
                    {
                        return false;
                    }
                }
                else
                {
                    // Collection, not interested whether list/array/etc are same object, just that they contain equal elements
                    // questioner guaranteed that all elements are unique               
                    HashSet<object> elements = new HashSet<object>();
                    foreach (object o in currentCollection)
                    {
                        elements.Add(o);
                    }

                    List<object> otherElements = new List<object>();
                    foreach (object o in (ICollection)otherValue)
                    {
                        otherElements.Add(o);
                    }

                    // cast below can be safely made because we have already asserted that
                    // current and other are the same type
                    if (!elements.SetEquals(otherElements))
                    {
                        return false;
                    }
                }
            }

            return true;
        }
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            BaseClass<int> a1 = new BaseClass<int>{prop1 = "Foo", prop2 = new List<int>{1, 2, 3}};
            BaseClass<int> a2 = new BaseClass<int>{prop1 = "Foo", prop2 = new List<int>{2, 1, 3}};
            BaseClass<int> a3 = new BaseClass<int>{prop1 = "Bar", prop2 = new List<int>{2, 1, 3}};
            BaseClass<string> a4 = new BaseClass<string>{prop1 = "Bar", prop2 = new List<string>()};
            BaseClass<int> a5 = new BaseClass<int>{prop1 = "Bar", prop2 = new List<int>{1, 3}};
            DateTime d1 = DateTime.Today;
            DateTime d2 = DateTime.Today;
            List<string> s1 = new List<string>{"s1", "s2"};
            string[] s2 = {"s1", "s2"};
            BaseClass<DayOfWeek> b1 = new BaseClass<DayOfWeek>{prop1 = "Bar", prop2 = new List<DayOfWeek>{DayOfWeek.Monday, DayOfWeek.Tuesday}};
            DescendentClass<DayOfWeek> b2 = new DescendentClass<DayOfWeek>{prop1 = "Bar", prop2 = new List<DayOfWeek>{DayOfWeek.Monday, DayOfWeek.Tuesday}};
            Console.WriteLine("a1 == a2 : " + a1.IsDeeplyEquivalent(a2)); // true, different order ignored
            Console.WriteLine("a1 == a3 : " + a1.IsDeeplyEquivalent(a3)); // false, different prop1
            Console.WriteLine("a3 == a4 : " + a3.IsDeeplyEquivalent(a4)); // false, different types
            Console.WriteLine("a3 == a5 : " + a3.IsDeeplyEquivalent(a5)); // false, difference in list elements
            Console.WriteLine("d1 == d2 : " + d1.IsDeeplyEquivalent(d2)); // true
            Console.WriteLine("s1 == s2 : " + s1.IsDeeplyEquivalent(s2)); // false, different types
            Console.WriteLine("b1 == b2 : " + s1.IsDeeplyEquivalent(s2)); // false, different types
        Console.WriteLine("b1 == b1 : " + b1.IsDeeplyEquivalent(b1)); // true, same object
            Console.ReadLine();
        }
    }
}
Adam G
  • 1,283
  • 1
  • 6
  • 15
  • this is what I had in mind, and it does what I want except replacing `if (currentValue == null && otherValue == null)` with `if (currentValue == null)`, and `if (currentValue == null || otherValue == null)` with `if (otherValue == null)` – havij Apr 17 '18 at 21:05