Сравните содержимое двух объектов для равенства

У меня есть два комплекса (т.е. объекты со строками, int, double, List и другие домашние типы данных) объекты того же типа. Я хотел бы сравнить их содержание и убедиться, что они идентичны. Примечание. Объект не реализует .Equals(у меня нет контроля над этим) и не реализует IComparable.

Существует ли общий способ (отражение?) для сравнения содержимого двух объектов?

Спасибо!

Ответ 1

Существует ли общий способ сравнения содержимого двух объектов?

Ну да, но обычно это известно как интерфейс IComparable.

Если вы могли бы спуститься с класса и создать ребенка, который реализовал IComparable, это может быть идеальным.

Ответ 3

Отражение было бы таким, но проблема заключается в содержащихся в ней типах - например, вы не можете просто использовать Equals или EqualityComparer<T>, так как суб-данные также не будут удобно сопоставимы, если это List<T> и т.д.

Как часто вам нужно это делать? Не могли бы вы их сериализовать и сравнить сериализованное значение? Это может быть наиболее надежный вариант.

Ответ 4

Мое рабочее решение.!

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;
}

Надеюсь, это поможет.!

Ответ 5

GetHashcode работает для меня.

Я переопределяю GetHashcode() в каждом классе со всеми общедоступными свойствами X-OR-ed например.

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

Я повторяю это через все классы, снова X-OR значения. IsModified сравнивает ранее HashValue с текущим. Два разных объекта действительно могут вернуть один и тот же HashValue с шансом от 1 до 4 миллиардов, но для многих целей это достаточно хорошо для меня.

Но у меня есть еще лучшая идея, используя MemoryStream

здесь есть расширение:

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;
    }
}

Ответ 6

Сериализуйте объекты в строку XML, и вы можете завершить сравнение строк между двумя объектами, которые сериализованы...

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;
            }
        }
    }

Ответ 7

Спасибо за подход MemoryStream, Марк. Я думал, что была ошибка, когда я увидел "this" в аргументах, но, на удивление, компилятор действительно позволяет вам так поступать, а? Я сделал небольшое изменение и вместо этого решил переопределить Equals(). Престижность также для использования сравнения длины и массива, а не SequenceEquals(). Требуется дополнительная минута, но, согласно http://www.dotnetperls.com/sequenceequal, производительность намного лучше.

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;
}

Ответ 8

Для моего последнего проекта я сделал хороший глубокий компилятор с некоторыми функциями.

    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;
        }
    }

Ответ 9

Я только что написал свою версию. Эта функция использует общий и рефлексивный характер. Он работает рекурсивным образом, пока все вещи в объекте не сравниваются или не обнаруживают тот, который не равен.

        public static 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;
    }

Ответ 10

Вы можете просто написать метод утилиты в другом классе для сравнения. Однако предполагается, что свойства рассматриваемого класса являются общедоступными. Они?

Ответ 11

Ну, вы могли бы написать некоторую логику, чтобы сравнить все свойства двух объектов друг с другом. Это становится сложным, когда это графа объектов со сложными подтипами, поэтому вам нужно будет определить, насколько близко это достаточно.

Ответ 12

Вам нужен метод сравнения чего-то другого; в С++ вы могли бы просто написать глобальную функцию, но я не думаю, что С# допускает это, как и Java.

Что бы я сделал, это написать класс, который реализует iComparable, и имеет ctor, который принимает объект вашего желаемого класса и включает в себя вашу функцию Equals. Установите его так, чтобы все, что он хранит, ссылается на исходный объект, чтобы сохранить mallocations.
Затем вы можете написать

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

Вместо этого вы можете наследовать из предоставленного класса, но это будет означать необходимость создания и отслеживания этих вещей.