比较两个对象的内容是否相等

时间:2008-12-17 20:55:17

标签: c#

我有两个相同类型的复杂(即具有string,int,double,List和其他自制数据类型的对象)对象。我想比较它们的内容,以确保它们是相同的。注意:该对象没有实现.Equals(我无法控制)并且没有实现IComparable。

是否有通用方法(反射?)来比较两个对象的内容?

谢谢!

13 个答案:

答案 0 :(得分:14)

我创建了一个类来执行.NET Objects的深度比较。参见:

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

答案 1 :(得分:6)

我的工作解决方案。!

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

希望它有所帮助。!

答案 2 :(得分:3)

  
    

是否有通用的方法来比较两个对象的内容?

  

是的,但通常称为IComparable接口。

如果您可以从课程中下载并创建一个实现IComparable的子项,那可能是理想的。

答案 3 :(得分:3)

反思就是这样,但问题是包含的类型 - 例如,您不能只使用EqualsEqualityComparer<T>,因为子数据如果它是List<T>等,将无法方便地比较。

您多久需要这样做?你能序列化它们并比较序列化值吗?这可能是最强大的选择。

答案 4 :(得分:3)

GetHashcode适合我。

我在所有公共相关属性X-OR-ed的类中重写GetHashcode() e.g。

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

我遍历所有类,再次X-OR值。 IsModified将先前的HashValue与当前值进行比较。 两个不同的对象可能确实返回相同的HashValue,可能有1到40亿,但出于很多目的,这对我来说已经足够了。

但是我有一个更好的想法,使用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;
    }
}

答案 5 :(得分:3)

我刚写了我的版本。此函数使用泛型和反射。它的工作原理是自我递归,直到对象中的所有东西都已经比较或找到一个不相等的东西。

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

答案 6 :(得分:2)

将对象序列化为XML字符串,您最终可以对序列化的2个对象进行字符串比较...

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 :(得分:2)

对于我的上一个项目,我对一些功能做了很好的深入研究。

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

答案 8 :(得分:1)

您可以在另一个类中编写实用程序方法来进行比较。但是,假设所讨论的类的属性是公开可访问的。是吗?

答案 9 :(得分:1)

感谢MemoryStream方法,Marc。当我看到&#34;这个&#34;我确定有一个错误。在参数中,但令人惊讶的是编译器实际上让你这样做,对吧?我稍作改动,选择覆盖Equals()代替。 Kudos也使用长度和数组比较而不是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;
}

答案 10 :(得分:1)

我发现最快,最简单的方法是使用MessagePack序列化两个对象,然后比较字节数组。

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

答案 11 :(得分:0)

好吧,你可以编写一些逻辑来比较两个对象的所有属性。当它是具有复杂子类型的对象图时,这会变得复杂,因此您需要确定接近的程度。

答案 12 :(得分:0)

你需要一种别的比较方法;在C ++中你可以编写一个全局函数,但我不认为c#允许这样做,就像Java没有那样。

我要做的是写一个实现iComparable的类,并且有一个ctor,它接受你想要的类的对象,并包含你的Equals函数。设置它所以它保留的是对原始对象的引用,以保存mallocations 然后你可以写

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

您可以改为继承所提供的类,但这意味着需要创建和跟踪这些内容。