различия между двумя объектами в C # - PullRequest
7 голосов
/ 22 декабря 2010

Мне было интересно, как я могу найти разницу между двумя объектами одного и того же класса.Так что если у меня был класс Person с единственным отличием, то есть Age, он вернет поле / поля, которые отличаются.

Спасибо

Ответы [ 6 ]

9 голосов
/ 22 декабря 2010

Это не то, что C # (или .NET на самом деле) поддерживает напрямую, однако вы можете реализовать что-то вручную для определенных типов или написать код, который использует отражение для сравнения произвольных объектов.

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

Написание алгоритма дифференцирования на основе отражения сложнее, чем кажется на первый взгляд - лично я реализовал бы эту функциональность непосредственно для типов (или внутри вспомогательного класса), где он вам нужен.

8 голосов
/ 24 декабря 2010

Вот простой код, который я использую для отладки:

    //This structure represents the comparison of one member of an object to the corresponding member of another object.
    public struct MemberComparison
    {
        public readonly MemberInfo Member; //Which member this Comparison compares
        public readonly object Value1, Value2;//The values of each object's respective member
        public MemberComparison(MemberInfo member, object value1, object value2)
        {
            Member = member;
            Value1 = value1;
            Value2 = value2;
        }

        public override string ToString()
        {
            return Member.Name + ": " + Value1.ToString() + (Value1.Equals(Value2) ? " == " : " != ") + Value2.ToString();
        }
    }

    //This method can be used to get a list of MemberComparison values that represent the fields and/or properties that differ between the two objects.
    public List<MemberComparison> ReflectiveCompare<T>(T x, T y)
    {
        List<MemberComparison> list = new List<MemberComparison>();//The list to be returned

        foreach (MemberInfo m in typeof(T).GetMembers(BindingFlags.NonPublic | BindingFlags.Instance))
            //Only look at fields and properties.
            //This could be changed to include methods, but you'd have to get values to pass to the methods you want to compare
            if (m.MemberType == MemberTypes.Field)
            {
                FieldInfo field = (FieldInfo)m;
                var xValue = field.GetValue(x);
                var yValue = field.GetValue(y);
                if (!object.Equals(xValue, yValue))//Add a new comparison to the list if the value of the member defined on 'x' isn't equal to the value of the member defined on 'y'.
                    list.Add(new MemberComparison(field, yValue, xValue));
            }
            else if (m.MemberType == MemberTypes.Property)
            {
                var prop = (PropertyInfo)m;
                if (prop.CanRead && prop.GetGetMethod().GetParameters().Length == 0)
                {
                    var xValue = prop.GetValue(x, null);
                    var yValue = prop.GetValue(y, null);
                    if (!object.Equals(xValue, yValue))
                        list.Add(new MemberComparison(prop, xValue, yValue));
                }
                else//Ignore properties that aren't readable or are indexers
                    continue;
            }
        return list;
    }

Чтобы использовать его, ваш код может выглядеть примерно так:

public static void Main()
{
    MyObject object1 = new MyObject();
    MyObject object2 = new MyObject();
    // ...Code that changes object1 and/or object2...

    //Here's your answer: a list of what's different between the 2 objects, and each of their different values.
    //No type parameters are needed here- typeof(MyObject) is implied by the coincident types of both parameters.
    List<MemberComparison> changes = ReflectiveCompare(object1, object2);
}
4 голосов
/ 22 декабря 2010

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

public class Pair
{
    public object Value1
    {
        get;
        set;
    }

    public object Value2
    {
        get;
        set;
    }
}

//somewhere in another class

public Dictionary<string, Pair> Compare<T>(T object1, T object2)
{
    var props = typeof(T).GetProperties().Where(pi => pi.CanRead); //this will return only public readable properties. Modify if you need something different
    Dictionary<string, Pair> result = new Dictionary<string, Pair>();
    foreach (var prop in props)
    {
        var val1 = prop.GetValue(object1, null); //indexing properties are ignored here
        var val2 = prop.GetValue(object2, null);
        if (val1 != val2) //maybe some more sophisticated compare algorithm here, using IComparable, nested objects analysis etc.
        {
            result[prop.Name] = new Pair { Value1 = val1, Value2 = val2 };
        }
    }
    return result;
}

Если вам нужно глубоко обработать вложенные объекты, то, как было сказано ранее, вам понадобится некоторая хеш-таблица, которая запомнит уже обработанные объекты и предотвратит повторную обработку. Надеюсь это поможет!

3 голосов
/ 17 июня 2013

Я использовал ответ Михаэля Хоффмана, однако обнаружил, что ему не хватает поддержки, если одно из свойств имеет значение null, и если выдается ошибка (обычно обнаруживается при сравнении объектов типа «Тип») или если это коллекция.

пока еще предстоит проделать работу, я публикую здесь базовый модифицированный код:

 public struct MemberComparison
    {
        public readonly System.Reflection.MemberInfo Member; //Which member this Comparison compares
        public readonly object Value1, Value2;//The values of each object's respective member
        public readonly Exception Value1Exception, Value2Exception;
        public MemberComparison(System.Reflection.MemberInfo member, object value1, object value2, Exception value1Exception = null, Exception value2Exception = null)
        {
            Member = member;
            Value1 = value1;
            Value2 = value2;
            Value1Exception = value1Exception;
            Value2Exception = value2Exception;
        }

        public override string ToString()
        {
            if (Value1Exception != null && Value2Exception != null)
            {
                if (Value1Exception.GetType().Equals(Value2Exception.GetType()))
                {
                     return Member.Name + ": Exception in both, same exception type of type "+Value1Exception.GetType().Name+", message in first exception: " +Value1Exception.Message+", message in second exception: "+Value2Exception.Message+", differences in type value: " + string.Join("\n", ReflectiveCompare(Value1Exception, Value2Exception).ToArray());
                }
                else if (!Value1Exception.GetType().Equals(Value2Exception.GetType()))
                {
                    return Member.Name + ": Exception in both, different exception type: " + Value1Exception.GetType().Name + " : " + Value2Exception.GetType().Name+", message in first exception: " +Value1Exception.Message+", message in second exception: "+Value2Exception.Message;
                }                    
            }
            else if (Value1Exception != null && Value2Exception == null)
            {                    
                   return Member.Name + ": "+ Value2.ToString()+" Exception in first of type " + Value1Exception.GetType().Name+", message is: "+Value1Exception.Message;
            } 
            else if (Value1Exception == null && Value2Exception != null)
            {                    
                   return Member.Name + ": "+ Value1.ToString()+" Exception in second of type " + Value2Exception.GetType().Name+", message is: "+Value2Exception.Message;
            }                
            return Member.Name + ": " + Value1.ToString() + (Value1.Equals(Value2) ? " == " : " != ") + Value2.ToString();
        }
    }


    public static bool isCollection(object obj)
    {
        return obj.GetType().GetInterfaces()
    .Any(iface => (iface.GetType() == typeof(ICollection) || iface.GetType() == typeof(IEnumerable) || iface.GetType() == typeof(IList)) || (iface.IsGenericTypeDefinition && (iface.GetGenericTypeDefinition() == typeof(ICollection<>) || iface.GetGenericTypeDefinition() == typeof(IEnumerable<>) || iface.GetGenericTypeDefinition() == typeof(IList<>))));
    }

//This method can be used to get a list of MemberComparison values that represent the fields and/or properties that differ between the two objects.
public static List<MemberComparison> ReflectiveCompare<T>(T x, T y)
{
    List<MemberComparison> list = new List<MemberComparison>();//The list to be returned

    var memb = typeof(T).GetMembers();
    foreach (System.Reflection.MemberInfo m in memb)
        //Only look at fields and properties.
        //This could be changed to include methods, but you'd have to get values to pass to the methods you want to compare
        if (m.MemberType == System.Reflection.MemberTypes.Field)
        {
            System.Reflection.FieldInfo field = (System.Reflection.FieldInfo)m;
            Exception excep1 = null;
            Exception excep2 = null;
            object xValue = null;
            object yValue = null;
            try
            {
                xValue = field.GetValue(x);
            }
            catch (Exception e)
            {
                excep1 = e;
            }
            try
            {
                yValue = field.GetValue(y);
            }
            catch (Exception e)
            {
                excep2 = e;
            }
            if ((excep1 != null && excep2 == null) || (excep1 == null && excep2 != null)) { list.Add(new MemberComparison(field, yValue, xValue, excep1, excep2)); }
            else if (excep1 != null && excep2 != null && !excep1.GetType().Equals(excep2.GetType())) { list.Add(new MemberComparison(field, yValue, xValue, excep1, excep2)); }
            else if (excep1 != null && excep2 != null && excep1.GetType().Equals(excep2.GetType()) && ReflectiveCompare(excep1, excep2).Count > 0) { list.Add(new MemberComparison(field, yValue, xValue, excep1, excep2)); }
            else if ((xValue == null && yValue == null)) { continue; }
            else if (xValue == null || yValue == null) list.Add(new MemberComparison(field, yValue, xValue));
            else if (!xValue.Equals(yValue) && ((!isCollection(xValue) && !isCollection(yValue)) || (isCollection(xValue) && !isCollection(yValue)) || (!isCollection(xValue) && isCollection(yValue)) || (isCollection(xValue) && isCollection(yValue) && ReflectiveCompare(xValue, yValue).Count > 0)))//Add a new comparison to the list if the value of the member defined on 'x' isn't equal to the value of the member defined on 'y'.
                list.Add(new MemberComparison(field, yValue, xValue));
        }
        else if (m.MemberType == System.Reflection.MemberTypes.Property)
        {
            var prop = (System.Reflection.PropertyInfo)m;
            if (prop.CanRead && !(prop.GetGetMethod() == null || prop.GetGetMethod().GetParameters() == null) && prop.GetGetMethod().GetParameters().Length == 0)
            {                    
                Exception excep1 = null;
                Exception excep2 = null;
                object xValue = null;
                object yValue = null;
                try
                {
                    xValue = prop.GetValue(x, null);
                }
                catch (Exception e)
                {
                    excep1 = e;
                }
                try
                {
                    yValue = prop.GetValue(y, null);
                }
                catch (Exception e)
                {
                    excep2 = e;
                }
                if ((excep1 != null && excep2 == null) || (excep1 == null && excep2 != null)) { list.Add(new MemberComparison(prop, yValue, xValue, excep1, excep2)); }
                else if (excep1 != null && excep2 != null && !excep1.GetType().Equals(excep2.GetType())) { list.Add(new MemberComparison(prop, yValue, xValue, excep1, excep2)); }
                else if (excep1 != null && excep2 != null && excep1.GetType().Equals(excep2.GetType()) && ReflectiveCompare(excep1, excep2).Count > 0) { list.Add(new MemberComparison(prop, yValue, xValue, excep1, excep2)); }
                else if ((xValue == null && yValue == null)) { continue; }
                else if (xValue == null || yValue == null) list.Add(new MemberComparison(prop, yValue, xValue));
                else if (!xValue.Equals(yValue) && ((!isCollection(xValue) && !isCollection(yValue)) || (isCollection(xValue) && !isCollection(yValue)) || (!isCollection(xValue) && isCollection(yValue)) || (isCollection(xValue) && isCollection(yValue) && ReflectiveCompare(xValue,yValue).Count > 0)))// || (isCollection(xValue) && isCollection(yValue)  && ((IEnumerable<T>)xValue).OrderBy(i => i).SequenceEqual(xValue.OrderBy(i => i))) )))
                    list.Add(new MemberComparison(prop, xValue, yValue));
            }
            else//Ignore properties that aren't readable or are indexers
                continue;
        }
    return list;
        }
2 голосов
/ 22 декабря 2010

Как насчет этого

Это дает вам список имен свойств, которые отличаются между двумя объектами. Я не думаю, что это путь к тому решению, которое вы ищете, но я думаю, что это достойное начало

Foo foo1 = new Foo { Prop1 = "One", Prop2 = "Two"};
Foo foo2 = new Foo { Prop1 = "One", Prop2 = "Three" };

Type fooType = typeof (Foo);

PropertyInfo[] properties = fooType.GetProperties();

var diffs = from property in properties
  let first = foo1
  let second = foo2
  where property.GetValue(first, null) != property.GetValue(second, null)
  select property;

В моем примере это вернет «Prop2», так как это свойство, значения которого отличаются между объектами.

EDIT : Конечно, это предполагает, что любые сложные типы в вашем объекте реализуют сравнения на равенство, которые делают то, что вы ожидаете. Если нет, то вам нужно было бы погрузиться в граф объектов и выполнить вложенное сравнение, как предлагали другие

1 голос
/ 22 декабря 2010

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

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

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