简体   繁体   中英

Compare the content of two objects for equality

I have two complex (ie objects with string, int, double, List and other home made data type) objects of the same type. I would like to compare the content of both of them to ensure that they are identical. Note: The object doesn't implement .Equals (I have no control on that) and doesn't implement IComparable.

Is there a generic way (reflection?) to compare the content of the two objects?

Thanks!

I have created a class to perform a deep compare of .NET Objects. See:

https://github.com/GregFinzer/Compare-Net-Objects

My Working solution.!

private bool Compare(object obj1, object obj2)
{
    if (obj1 == null || obj2 == null)
    {
        return false;
    }
    if (!obj1.GetType().Equals(obj2.GetType()))
    {
        return false;
    }

    Type type = obj1.GetType();
    if (type.IsPrimitive || typeof(string).Equals(type))
    {
        return obj1.Equals(obj2);
    }
    if (type.IsArray)
    {
        Array first = obj1 as Array;
        Array second = obj2 as Array;
        var en = first.GetEnumerator();
        int i = 0;
        while (en.MoveNext())
        {
            if (!Compare(en.Current, second.GetValue(i)))
                return false;
            i++;
        }
    }
    else
    {
        foreach (PropertyInfo pi in type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
        {
            var val = pi.GetValue(obj1);
            var tval = pi.GetValue(obj2);
            if (!Compare(val, tval))
                return false;
        }
        foreach (FieldInfo fi in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
        {
            var val = fi.GetValue(obj1);
            var tval = fi.GetValue(obj2);
            if (!Compare(val, tval))
                return false;
        }
    }
    return true;
}

Hope it helps.!

GetHashcode works for me.

I override GetHashcode() in every class with all public relevant properties X-OR-ed eg

override GetHashCode()
{
   return A.GetHashCode() ^ B.GetHashCode ^ C.SafeString().Get..
}

I iterate this through all classes, again X-OR the values. IsModified compares a previously HashValue with the current. Two different objects could indeed return the same HashValue, with a chance of 1 to 4 billion, but for many purposes this is good enough for me.

But I have an even better idea, using a MemoryStream

here is an Extension:

public static bool IsBinaryEqualTo(this object obj, object obj1)
{
    using (MemoryStream memStream = new MemoryStream())
    {
        if (obj == null || obj1 == null)
        {
            if (obj == null && obj1 == null)
                return true;
            else
                return false;
        }

        BinaryFormatter binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
        binaryFormatter.Serialize(memStream, obj);
        byte[] b1 = memStream.ToArray();
        memStream.SetLength(0);

        binaryFormatter.Serialize(memStream, obj1);
        byte[] b2 = memStream.ToArray();

        if (b1.Length != b2.Length)
            return false;

        for (int i = 0; i < b1.Length; i++)
        {
            if (b1[i] != b2[i])
                return false;
        }

        return true;
    }
}

I just wrote my version. This function use generic and reflection. It works by recursive it self until all of things in the object already compare or found one that do not equal.

public class Utils
{
    public bool CompareObjects<T>(T expectInput, T actualInput)
    {
        // If T is primitive type.
        if (typeof(T).IsPrimitive)
        {
            if (expectInput.Equals(actualInput))
            {
                return true;
            }

            return false;
        }

        if (expectInput is IEquatable<T>)
        {
            if (expectInput.Equals(actualInput))
            {
                return true;
            }

            return false;
        }

        if (expectInput is IComparable)
        {
            if (((IComparable)expectInput).CompareTo(actualInput) == 0)
            {
                return true;
            }

            return false;
        }

        // If T is implement IEnumerable.
        if (expectInput is IEnumerable)
        {
            var expectEnumerator = ((IEnumerable)expectInput).GetEnumerator();
            var actualEnumerator = ((IEnumerable)actualInput).GetEnumerator();

            var canGetExpectMember = expectEnumerator.MoveNext();
            var canGetActualMember = actualEnumerator.MoveNext();

            while (canGetExpectMember && canGetActualMember && true)
            {
                var currentType = expectEnumerator.Current.GetType();
                object isEqual = typeof(Utils).GetMethod("CompareObjects").MakeGenericMethod(currentType).Invoke(null, new object[] { expectEnumerator.Current, actualEnumerator.Current });

                if ((bool)isEqual == false)
                {
                    return false;
                }

                canGetExpectMember = expectEnumerator.MoveNext();
                canGetActualMember = actualEnumerator.MoveNext();
            }

            if (canGetExpectMember != canGetActualMember)
            {
                return false;
            }

            return true;
        }

        // If T is class.
        var properties = typeof(T).GetProperties();
        foreach (var property in properties)
        {
            var expectValue = typeof(T).GetProperty(property.Name).GetValue(expectInput);
            var actualValue = typeof(T).GetProperty(property.Name).GetValue(actualInput);

            if (expectValue == null || actualValue == null)
            {
                if (expectValue == null && actualValue == null)
                {
                    continue;
                }

                return false;
            }

            object isEqual = typeof(Utils).GetMethod("CompareObjects").MakeGenericMethod(property.PropertyType).Invoke(null, new object[] { expectValue, actualValue });

            if ((bool)isEqual == false)
            {
                return false;
            }
        }

        return true;
    }
}

Is there a generic way to compare the content of the two objects?

Well yes, but generally that's known as the IComparable interface.

If you could descend from the class and create a child that implemented IComparable, that might be ideal.

Reflection would be it, but the issue is the contained types - for example, you can't just use Equals or EqualityComparer<T> , since the sub-data also won't be conveniently comparable if it is a List<T> etc.

How often do you need to do this? Could you serialize them and compare the serialized value? That might be the most robust option.

Serialize the objects to XML string and you can end up doing a string comparison between the 2 objects that are serialized...

private string Serialize<T>(T value)
        {
            if (value == null)
            {
                return string.Empty;
            }
            try
            {
                XmlSerializer xmlserializer = new XmlSerializer(typeof(T));
                StringWriter stringWriter = new StringWriter();
                XmlWriter writer = XmlWriter.Create(stringWriter);
                xmlserializer.Serialize(writer, value);
                string serializeXml = stringWriter.ToString();
                writer.Close();               
                return serializeXml;
            }
            catch (Exception ex)
            {
                return string.Empty;
            }
        }
    }

For my last project I made a nice deep compair with some features.

    public class ObjektHelper
    {

        /// <summary>
        /// Compairs two Objects and gives back true if they are equal
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj1">Object 1</param>
        /// <param name="obj2">Object 2</param>
        /// <param name="consideredFieldNames">If the list is not mepty, only the field within equal names are compaired.</param>
        /// <param name="notConsideredFieldNames">If you want not compair some fields enter their name in this list.</param>
        /// <returns></returns>
        public static bool DeepCompare<T>(T obj1, T obj2, string[] consideredFieldNames, params string[] notConsideredFieldNames)
        {
            var errorList = new List<object>();
            if (notConsideredFieldNames == null) notConsideredFieldNames = new[] {""};
            DeepCompare(obj1, obj2, errorList, consideredFieldNames, notConsideredFieldNames, false);
            return errorList.Count <= 0;
        }

        /// <summary>
        /// Compairs two Objects and gives an error list back.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj1"></param>
        /// <param name="obj2"></param>
        /// <param name="errorList">The error list gives back the names of the fields that are not equal.</param>
        /// <param name="consideredFieldNames">If the list is not mepty, only the field within equal names are compaired.</param>
        /// <param name="notConsideredFieldNames">If you want not compair some fields enter their name in this list.</param>
        /// <param name="endWithErrors">If the value is false, the method end at the first error.</param>
        public static void DeepCompare<T>(T obj1, T obj2, List<object> errorList, string[] consideredFieldNames, string[] notConsideredFieldNames, bool endWithErrors)
        {
            if (errorList == null) throw new Exception("errorListliste ist NULL");

            if (Equals(obj1, default(T)) && Equals(obj2, default(T))) return;
            if (Equals(obj1, default(T)) || Equals(obj2, default(T)))
            {
                errorList.Add("One of the object are null!");
                return;
            }
            if (!endWithErrors && errorList != null && errorList.Count > 0) throw new Exception("ErrorListliste is not empty");
            var type1 = obj1.GetType();
            var type2 = obj2.GetType();
            var propertyInfos1 = type1.GetProperties();
            var propertyInfos2 = type2.GetProperties();
            // To use the access via index, the list have to be ordered!
            var propertyInfoOrdered1 = propertyInfos1.OrderBy(p => p.Name).ToArray();
            var propertyInfoOrdered2 = propertyInfos2.OrderBy(p => p.Name).ToArray();

            if (type1 != type2) errorList.AddRange(new List<object> {type1, type2});
            else
            {
                for (var i = 0; i < propertyInfos1.Length; i++)
                {
                    var t1 = propertyInfoOrdered1[i].PropertyType;
                    var t2 = propertyInfoOrdered2[i].PropertyType;
                    if (t1 != t2)
                    {
                        errorList.AddRange(new List<object> {type1, type2});
                        continue;
                    }

                    var name1 = propertyInfoOrdered1[i].Name;
                    var name2 = propertyInfoOrdered2[i].Name;

                    // Use the next 4 lines to find a bug
                    //if (name1 == "Enter name of field with the bug")
                    //    Console.WriteLine(name1);
                    //if (name2 == "Enter name of field1 with the bug" || name2 == "Enter name of field2 with the bug")
                    //    Console.WriteLine(name2);

                    if (consideredFieldNames != null && !consideredFieldNames.Contains(name1)) continue;
                    if (notConsideredFieldNames != null && notConsideredFieldNames.Contains(name1)) continue;

                    var value1 = propertyInfoOrdered1[i].GetValue(obj1, null);
                    var value2 = propertyInfoOrdered2[i].GetValue(obj2, null);

                    // check Attributes
                    var guiName1 = (propertyInfoOrdered1[i].GetCustomAttributes().FirstOrDefault(a => a.GetType() == typeof(GuiNameofModelAttribute)) as GuiNameofModelAttribute)?.GuiName;
                    var guiName2 = (propertyInfoOrdered2[i].GetCustomAttributes().FirstOrDefault(a => a.GetType() == typeof(GuiNameofModelAttribute)) as GuiNameofModelAttribute)?.GuiName;
                    // create errorListrange
                    var temperrorListRange = new List<object>();
                    if (guiName1 != null && guiName2 != null) temperrorListRange.AddRange(new List<object> { guiName1, guiName2 });
                    else temperrorListRange.AddRange(new List<object> { propertyInfoOrdered1[i], propertyInfoOrdered2[i] });

                    // both fields are null = OK
                    if ((value1 == null && value2 == null) || (value1 is Guid && value2 is Guid)) continue;
                    // one of the fields is null = errorList
                    if (value1 == null || value2 == null) errorList.AddRange(temperrorListRange);
                    // Value types, Enum and String compair
                    else if (t1.BaseType == typeof (ValueType) || t1.BaseType == typeof (Enum) || t1 == typeof (string))
                    {
                        if (!value1.Equals(value2)) errorList.AddRange(temperrorListRange);
                    }
                    // List, array, generic lists, collection and bindinglist compair    
                    else if (t1 == typeof (Array) || (t1.IsGenericType && (t1.GetGenericTypeDefinition() == typeof (List<>) ||
                                                                           t1.GetGenericTypeDefinition() == typeof (IList<>) ||
                                                                           t1.GetGenericTypeDefinition() == typeof (Collection<>) ||
                                                                           t1.GetGenericTypeDefinition() == typeof (ICollection<>) ||
                                                                           t1.GetGenericTypeDefinition() == typeof (ObservableCollection<>) ||
                                                                           t1.GetGenericTypeDefinition() == typeof (BindingList<>) ||
                                                                           t1.GetGenericTypeDefinition() == typeof (BindingList<>)
                        )))
                        DeepListCompare(value1 as IEnumerable<object>, value2 as IEnumerable<object>, errorList, consideredFieldNames, notConsideredFieldNames, endWithErrors);

                    // Clas compair
                    else if (t1.IsClass || t1.IsAnsiClass) DeepCompare(value1, value2, errorList, consideredFieldNames, notConsideredFieldNames, endWithErrors);
                    else throw new NotImplementedException();

                    if (!endWithErrors && errorList.Count > 0) break;
                }

            }

        } // End DeepCompare<T>

        /// <summary>
        /// Compairs two lists and gives back true if they are equal.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tlist1">Generic list 1</param>
        /// <param name="tlist2">Generic List 2</param>
        /// <returns></returns>
        public static bool DeepListCompare<T>(T tlist1, T tlist2)
        {
            var errorList = new List<object>();
            DeepCompare(tlist1, tlist2, errorList, null, null, false);
            return errorList.Count <= 0;
        }

        /// <summary>
        /// Compairs two lists and gives backthe error list.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tlist1">Generic list 1</param>
        /// <param name="tlist2">Generic list 2</param>
        /// <param name="errorList">The error list gives back the names of the fields that are not equal.</param>
        /// <param name="consideredFieldNames">If the list is not mepty, only the field within equal names are compaired.</param>
        /// <param name="notConsideredFieldNames">If you want not compair some fields enter their name in this list.</param>
        /// <param name="endWithErrors">If the value is false, the method end at the first error.</param>
        public static void DeepListCompare<T>(T tlist1, T tlist2, List<object> errorList, string[] consideredFieldNames, string[] notConsideredFieldNames, bool endWithErrors)
            where T : IEnumerable<object>
        {
            if (errorList == null) throw new Exception("errorListliste ist NULL");

            if (!endWithErrors && errorList.Count > 0) throw new Exception("errorListliste ist nicht Leer");
            if (Equals(tlist1, null) || Equals(tlist2, null))
            {
                errorList.AddRange(new List<object> {tlist1, tlist2});
                return;
            }
            var type1 = tlist1.GetType();
            var type2 = tlist2.GetType();
            var propertyInfos1 = type1.GetProperties();
            var propertyInfos2 = type2.GetProperties();

            // To use the access via index, the list have to be ordered!
            var propertyInfoOrdered1 = propertyInfos1.OrderBy(p => p.Name).ToArray();
            var propertyInfoOrdered2 = propertyInfos2.OrderBy(p => p.Name).ToArray();

            for (var i = 0; i < propertyInfos1.Length; i++)
            {
                var t1 = propertyInfoOrdered1[i].PropertyType;
                var t2 = propertyInfoOrdered2[i].PropertyType;

                if (t1 != t2) errorList.AddRange(new List<object> {t1, t2});
                else
                {
                    // Kick out index
                    if (propertyInfoOrdered1[i].GetIndexParameters().Length != 0)
                    {
                        continue;
                    }

                    // Get value
                    var value1 = propertyInfoOrdered1[i].GetValue(tlist1, null) as IEnumerable<object>;
                    var value2 = propertyInfoOrdered2[i].GetValue(tlist2, null) as IEnumerable<object>;

                    if (value1 == null || value2 == null) continue;

                    // Only run through real lists.
                    if (t1 == typeof (Array) ||
                        t1.IsGenericType && t1.GetGenericTypeDefinition() == typeof (List<>) ||
                        t1.IsGenericType && t1.GetGenericTypeDefinition() == typeof (Collection<>))
                    {
                        // cast 
                        var objectList1 = value1.ToList();
                        var objectList2 = value2.ToList();
                        if (objectList1.Count == 0 && objectList1.Count == 0)
                        {
                            //errorList.AddRange(new List<Object> { objectList1, objectList1 });
                            continue;
                        }

                        foreach (var item1 in objectList1)
                        {
                            foreach (var item2 in objectList2)
                            {
                                var temperrorListCount = errorList.Count;
                                DeepCompare(item1, item2, errorList, consideredFieldNames, notConsideredFieldNames, endWithErrors);
                                if (temperrorListCount != errorList.Count) continue;

                                objectList2.Remove(item2);
                                break;
                            }
                            if (!endWithErrors && errorList.Count > 0) break;
                        }
                    }
                    else DeepCompare(value1, value2, errorList, consideredFieldNames, notConsideredFieldNames, endWithErrors);
                }
                if (!endWithErrors && errorList.Count > 0) break;
            }

        } // End DeepListCompare<T>


    } // end class ObjectHelper


    [AttributeUsage(AttributeTargets.All)]
    public class GuiNameofModelAttribute : Attribute
    {
        public readonly string GuiName;

        public GuiNameofModelAttribute(string guiName)
        {
            GuiName = guiName;
        }
    }

The fastest and easiest way I've found is to serialize both objects using MessagePack then compare the byte arrays.

public static bool DeepEquals(object o1, object o2)
{
    var b1 = MessagePackSerializer.Serialize(o1, ContractlessStandardResolver.Instance);
    var b2 = MessagePackSerializer.Serialize(o2, ContractlessStandardResolver.Instance);
    return b1.SequenceEqual(b2);
}

Thanks for the MemoryStream approach, Marc. I thought sure there was a mistake in when I saw "this" in the arguments, but surprisingly the compiler actually lets you do it that way, huh? I made a slight change and chose to override to Equals() instead. Kudos also for using length and array comparison rather than SequenceEquals(). Takes an extra minute to write, but according to http://www.dotnetperls.com/sequenceequal , the performance is much better.

public override bool Equals(object obj)
{
    // If comparison object is null or is a different type, no further comparisons are necessary...
    if (obj == null || GetType() != obj.GetType())
    {
        return false;
    }

    // Compare objects using byte arrays...
    using (MemoryStream memStream = new MemoryStream())
    {
        BinaryFormatter binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));

        // Get byte array of "this" object...
        binaryFormatter.Serialize(memStream, this);
        byte[] b1 = memStream.ToArray();

        // Get byte array of object to be compared...
        memStream.SetLength(0);
        binaryFormatter.Serialize(memStream, obj);
        byte[] b2 = memStream.ToArray();

        // Compare array sizes. If equal, no further comparisons are necessary...
        if (b1.Length != b2.Length)
            return false;

        // If sizes are equal, compare each byte while inequality is not found...
        for (int i = 0; i < b1.Length; i++)
        {
            if (b1[i] != b2[i])
                return false;
        }
    }

    return true;
}

You could simply write a utility method in another class to do the comparison. However, that is assuming the properties of the class in question are publicly accessible. Are they?

Well, you could write some logic to compare all of the properties of the two objects to each other. This gets complicated when it is an object graph with complex subtypes, so you will need to determine how close is good enough.

You would need a compare method of something else; in C++ you could just write a global function, but I don't think c# allows that, just as Java doesn't.

What I'd do is write a class that implements iComparable, and has a ctor that takes an object of your desired class, and includes your Equals function. Set it up so all it keeps is a reference to the original object, to save mallocations.
Then you can write

Foo(A).Equals(new Foo(B))

You could instead inherit from the provided class, but that would mean needing to create and track these things.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM