Сравнение свойств объекта в c # [закрыто]

111

Это то, что я придумал в качестве метода для класса, унаследованного от многих других моих классов. Идея состоит в том, что он позволяет просто сравнивать свойства объектов одного типа.

Теперь это действительно работает, но в интересах повышения качества моего кода я подумал, что брошу его для проверки. Как это может быть лучше / эффективнее / и т. Д.?

/// <summary>
/// Compare property values (as strings)
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public bool PropertiesEqual(object comparisonObject)
{

    Type sourceType = this.GetType();
    Type destinationType = comparisonObject.GetType();

    if (sourceType == destinationType)
    {
        PropertyInfo[] sourceProperties = sourceType.GetProperties();
        foreach (PropertyInfo pi in sourceProperties)
        {
            if ((sourceType.GetProperty(pi.Name).GetValue(this, null) == null && destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null) == null))
            {
                // if both are null, don't try to compare  (throws exception)
            }
            else if (!(sourceType.GetProperty(pi.Name).GetValue(this, null).ToString() == destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null).ToString()))
            {
                // only need one property to be different to fail Equals.
                return false;
            }
        }
    }
    else
    {
        throw new ArgumentException("Comparison object must be of the same type.","comparisonObject");
    }

    return true;
}
гвоздь
источник
2
См. Также: stackoverflow.com/questions/3060382/… и, возможно, stackoverflow.com/questions/986572/…
Марк Гравелл
3
Кстати, вы знаете об этом сайте SE: codereview.stackexchange.com
wip
Есть несколько библиотек сравнения объектов: CompareNETObjects , DeepEqual , AutoCompare , ZCompare ...
nawfal
... и множество универсальных средств реализации компараторов равенства, некоторые из которых: MemberwiseEqualityComparer , Equ , SemanticComparison , EqualityComparer , DeepEqualityComparer , Equality , Equals.Fody . Последняя группа может быть ограничена по объему и гибкости в отношении того, чего они могут достичь.
nawfal
Я голосую за то, чтобы закрыть этот вопрос как не по теме, потому что он относится к обзору кода
Xiaoy312

Ответы:

160

Я искал фрагмент кода, который делал бы что-то подобное, чтобы помочь с написанием модульного теста. Вот что я в итоге использовал.

public static bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class 
  {
     if (self != null && to != null)
     {
        Type type = typeof(T);
        List<string> ignoreList = new List<string>(ignore);
        foreach (System.Reflection.PropertyInfo pi in type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
        {
           if (!ignoreList.Contains(pi.Name))
           {
              object selfValue = type.GetProperty(pi.Name).GetValue(self, null);
              object toValue = type.GetProperty(pi.Name).GetValue(to, null);

              if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
              {
                 return false;
              }
           }
        }
        return true;
     }
     return self == to;
  }

РЕДАКТИРОВАТЬ:

Тот же код, что и выше, но использует методы LINQ и Extension:

public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var ignoreList = new List<string>(ignore);
        var unequalProperties =
            from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
            where !ignoreList.Contains(pi.Name) && pi.GetUnderlyingType().IsSimpleType() && pi.GetIndexParameters().Length == 0
            let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
            let toValue = type.GetProperty(pi.Name).GetValue(to, null)
            where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
            select selfValue;
        return !unequalProperties.Any();
    }
    return self == to;
}

public static class TypeExtensions
   {
      /// <summary>
      /// Determine whether a type is simple (String, Decimal, DateTime, etc) 
      /// or complex (i.e. custom class with public properties and methods).
      /// </summary>
      /// <see cref="http://stackoverflow.com/questions/2442534/how-to-test-if-type-is-primitive"/>
      public static bool IsSimpleType(
         this Type type)
      {
         return
            type.IsValueType ||
            type.IsPrimitive ||
            new[]
            {
               typeof(String),
               typeof(Decimal),
               typeof(DateTime),
               typeof(DateTimeOffset),
               typeof(TimeSpan),
               typeof(Guid)
            }.Contains(type) ||
            (Convert.GetTypeCode(type) != TypeCode.Object);
      }

      public static Type GetUnderlyingType(this MemberInfo member)
      {
         switch (member.MemberType)
         {
            case MemberTypes.Event:
               return ((EventInfo)member).EventHandlerType;
            case MemberTypes.Field:
               return ((FieldInfo)member).FieldType;
            case MemberTypes.Method:
               return ((MethodInfo)member).ReturnType;
            case MemberTypes.Property:
               return ((PropertyInfo)member).PropertyType;
            default:
               throw new ArgumentException
               (
                  "Input MemberInfo must be if type EventInfo, FieldInfo, MethodInfo, or PropertyInfo"
               );
         }
      }
   }
Тарас Аленин
источник
Big T - довольно старый, но определенно служит отличной цели как для тестирования, так и для простых сравнений .. спасибо +1
Джим Толлан
1
Это хорошо, но я обнаружил, что не работает с более сложными объектами. Например, у меня есть объект с некоторыми строками (он отлично их сравнивает), но тогда этот объект также имеет список другого объекта, который он не сравнивает правильно, поэтому нужно как-то рекурсировать это.
Райан Томас,
1
Мне пришлось добавить к критериям в первом, где еще два критерия, потому что вам нужно исключить индексированные свойства, которые вызывают исключение в другом случае. Вот критерии для этой ошибки: pi.GetIndexParameters (). Length == 0. И второй критерий для решения проблемы, заявленной @RyanThomas, таков: pi.GetUnderlyingType (). IsSimpleType (). Как вы увидите, IsSimpleType - это расширение, которое не существует для класса Type. Я изменил ответ, добавив все эти условия и расширение.
Samuel
64

ОБНОВЛЕНИЕ: последняя версия Compare-Net-Objects находится на GitHub , имеет пакет NuGet и руководство . Это можно назвать

//This is the comparison class
CompareLogic compareLogic = new CompareLogic();

ComparisonResult result = compareLogic.Compare(person1, person2);

//These will be different, write out the differences
if (!result.AreEqual)
    Console.WriteLine(result.DifferencesString);

Или, если вам нужно изменить конфигурацию, используйте

CompareLogic basicComparison = new CompareLogic() 
{ Config = new ComparisonConfig()
   { MaxDifferences = propertyCount 
     //add other configurations
   }
};

Полный список настраиваемых параметров находится в ComparisonConfig.cs

Оригинальный ответ:

Ограничения, которые я вижу в вашем коде:

  • Самый большой из них заключается в том, что он не выполняет глубокого сравнения объектов.

  • Он не выполняет сравнение элементов по элементам, если свойства являются списками или содержат списки в виде элементов (это может быть n уровней).

  • При этом не учитывается, что некоторые типы свойств не должны сравниваться (например, свойство Func, используемое для целей фильтрации, например, свойство в классе PagedCollectionView).

  • Он не отслеживает, какие свойства на самом деле были разными (чтобы вы могли показать их в своих утверждениях).

Сегодня я искал какое-то решение для модульного тестирования, чтобы провести глубокое сравнение свойств, и в итоге я использовал: http://comparenetobjects.codeplex.com .

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

var compareObjects = new CompareObjects()
{
    CompareChildren = true, //this turns deep compare one, otherwise it's shallow
    CompareFields = false,
    CompareReadOnly = true,
    ComparePrivateFields = false,
    ComparePrivateProperties = false,
    CompareProperties = true,
    MaxDifferences = 1,
    ElementsToIgnore = new List<string>() { "Filter" }
};

Assert.IsTrue(
    compareObjects.Compare(objectA, objectB), 
    compareObjects.DifferencesString
);

Кроме того, его можно легко перекомпилировать для Silverlight. Просто скопируйте один класс в проект Silverlight и удалите одну или две строки кода для сравнений, которые недоступны в Silverlight, например, сравнение частных членов.

Ливиу Трифои
источник
2
Ливиу, я заметил ваш комментарий о несовместимости класса с Silverlight. Я просто изменил его, чтобы он был совместим с Silverlight и Windows Phone 7. Сделайте последнюю версию. См. Набор изменений 74131 на comparenetobjects.codeplex.com/SourceControl/list/changesets
Грег Финзер
Это выглядит многообещающе. Собираюсь попробовать
DJ Burb
Спасибо за отличный пример! Кроме того, IgnoreObjectTypesнастройка может быть полезна при наличии разных типов.
Сергей Брунов
Версия 2.0 имеет версию Portable Class Library, совместимую с Silverlight 5+, Windows Phone 8+, WinRT 8+, Xamarin IOS и Xamarin Droid
Грег Финзер
DifferencesStringисключен из класса CompareObjects. Но теперь вы можете получить это из ComparisonResult:var r = compareObjects.Compare(objectA, objectB); Assert.IsTrue(r.AreEqual, r.DifferencesString);
Мариано Десанце
6

Я думаю, что было бы лучше следовать шаблону для Override Object # Equals ().
Для лучшего описания: прочтите Эффективный C # Билла Вагнера - пункт 9, я думаю

public override Equals(object obOther)
{
  if (null == obOther)
    return false;
  if (object.ReferenceEquals(this, obOther)
    return true;
  if (this.GetType() != obOther.GetType())
    return false;
  # private method to compare members.
  return CompareMembers(this, obOther as ThisClass);
}
  • Также в методах, проверяющих равенство, вы должны возвращать либо true, либо false. либо они равны, либо нет .. вместо создания исключения верните false.
  • Я бы подумал о переопределении Object # Equals.
  • Несмотря на то, что вы, должно быть, обдумали это, использование Reflection для сравнения свойств предположительно медленное (у меня нет цифр, подтверждающих это). Это поведение по умолчанию для valueType # Equals в C #, и рекомендуется переопределить Equals для типов значений и выполнить поэлементное сравнение производительности. (Раньше я быстро прочитал это, поскольку у вас есть коллекция настраиваемых объектов Property ... плохо.)

Обновление - декабрь 2011 г .:

  • Конечно, если у типа уже есть продукция Equals (), вам нужен другой подход.
  • Если вы используете это для сравнения неизменяемых структур данных исключительно в целях тестирования, вам не следует добавлять Equals к производственным классам (кто-то может промыть тесты, выбрав реализацию Equals, или вы можете предотвратить создание необходимой для производства реализации Equals) .
Gishu
источник
У меня возникли проблемы с переопределением .Equals (), потому что я пытаюсь реализовать это в базовом классе, который наследуется ... потому что я не знаю ключей для класса, с которым он будет работать, я не могу реализовать достойное переопределение для GetHasCode () (требуется при переопределении Equals ()).
nailitdown
Требование состоит в том, что если objA.Equals (objB), то objA.GetHashCode () == objB.GetHashCode (). GetHashCode не должен зависеть от изменяемого состояния / данных класса ... Я не понял, что вы имели в виду под ключами для класса ... Похоже, что-то, что можно решить. Разве у базового типа нет «ключей»?
Gishu
6

Если производительность не имеет значения, вы можете сериализовать их и сравнить результаты:

var serializer = new XmlSerializer(typeof(TheObjectType));
StringWriter serialized1 = new StringWriter(), serialized2 = new StringWriter();
serializer.Serialize(serialized1, obj1);
serializer.Serialize(serialized2, obj2);
bool areEqual = serialized1.ToString() == serialized2.ToString();
Эдвард Брей
источник
4
попробовал это некоторое время назад, вы могли бы задаться вопросом, сколько объектов не сериализуемы ...
Offler
5

Я думаю, что ответ Big T был неплохим, но подробного сравнения не было, поэтому я немного его изменил:

using System.Collections.Generic;
using System.Reflection;

/// <summary>Comparison class.</summary>
public static class Compare
{
    /// <summary>Compare the public instance properties. Uses deep comparison.</summary>
    /// <param name="self">The reference object.</param>
    /// <param name="to">The object to compare.</param>
    /// <param name="ignore">Ignore property with name.</param>
    /// <typeparam name="T">Type of objects.</typeparam>
    /// <returns><see cref="bool">True</see> if both objects are equal, else <see cref="bool">false</see>.</returns>
    public static bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
    {
        if (self != null && to != null)
        {
            var type = self.GetType();
            var ignoreList = new List<string>(ignore);
            foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (ignoreList.Contains(pi.Name))
                {
                    continue;
                }

                var selfValue = type.GetProperty(pi.Name).GetValue(self, null);
                var toValue = type.GetProperty(pi.Name).GetValue(to, null);

                if (pi.PropertyType.IsClass && !pi.PropertyType.Module.ScopeName.Equals("CommonLanguageRuntimeLibrary"))
                {
                    // Check of "CommonLanguageRuntimeLibrary" is needed because string is also a class
                    if (PublicInstancePropertiesEqual(selfValue, toValue, ignore))
                    {
                        continue;
                    }

                    return false;
                }

                if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
                {
                    return false;
                }
            }

            return true;
        }

        return self == to;
    }
}
Greg
источник
4

Я бы добавил следующую строку в метод PublicInstancePropertiesEqual, чтобы избежать ошибок копирования и вставки:

Assert.AreNotSame(self, to);
танеи
источник
2

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

Кроме того, во втором сравнении я сомневаюсь в конструкции! (A == B) по сравнению с (A! = B) с точки зрения удобочитаемости через шесть месяцев / два года. Сама линия довольно широкая, что нормально, если у вас широкий монитор, но может не печатать очень хорошо. (придирка)

Все ли ваши объекты всегда используют свойства, чтобы этот код работал? Могут ли быть какие-то внутренние, не относящиеся к собственности данные, которые могут отличаться от одного объекта к другому, но все открытые данные одинаковы? Я думаю о некоторых данных, которые могут меняться со временем, например, о двух генераторах случайных чисел, которые случайно попадают в одно и то же число в один момент, но собираются произвести две разные последовательности информации или просто любые данные, которые не раскрываются. через интерфейс свойств.

MMR
источник
хорошие моменты -! = ... согласен, точка принята. ToString () был попыткой обхода .GetValue, возвращающего объект (таким образом, сравнение всегда ложно, так как это сравнение ссылок) .. есть ли лучший способ?
nailitdown
Если GetValue возвращает объект, можете ли вы снова пройти через эту функцию? т.е. вызвать PropertiesEqual для возвращаемых объектов?
mmr 03
1

Если вы сравниваете только объекты одного типа или ниже по цепочке наследования, почему бы не указать параметр в качестве базового типа, а не объекта?

Также выполните нулевые проверки параметра.

Кроме того, я бы использовал var, чтобы сделать код более читабельным (если это код С # 3)

Кроме того, если объект имеет ссылочные типы в качестве свойств, тогда вы просто вызываете для них ToString (), который на самом деле не сравнивает значения. Если ToString не перезаписан, он просто вернет имя типа в виде строки, которая может возвращать ложные срабатывания.

DarkwingDuck
источник
Хорошая точка зрения на ссылочные типы - в моем случае это не имеет значения, но есть большая вероятность, что это будет.
nailitdown
1

Первое, что я бы предложил, - это разделить фактическое сравнение, чтобы оно было более читабельным (я также убрал ToString () - это нужно?):

else {
    object originalProperty = sourceType.GetProperty(pi.Name).GetValue(this, null);
    object comparisonProperty = destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null);

    if (originalProperty != comparisonProperty)
        return false;

Следующее предложение - как можно меньше использовать отражение - это очень медленно. Я имею в виду, очень медленно. Если вы собираетесь это сделать, я бы посоветовал кэшировать ссылки на свойства. Я не очень хорошо знаком с Reflection API, поэтому, если это немного не так, просто настройте его, чтобы он скомпилировался:

// elsewhere
Dictionary<object, Property[]> lookupDictionary = new Dictionary<object, Property[]>;

Property[] objectProperties = null;
if (lookupDictionary.ContainsKey(sourceType)) {
  objectProperties = lookupProperties[sourceType];
} else {
  // build array of Property references
  PropertyInfo[] sourcePropertyInfos = sourceType.GetProperties();
  Property[] sourceProperties = new Property[sourcePropertyInfos.length];
  for (int i=0; i < sourcePropertyInfos.length; i++) {
    sourceProperties[i] = sourceType.GetProperty(pi.Name);
  }
  // add to cache
  objectProperties = sourceProperties;
  lookupDictionary[object] = sourceProperties;
}

// loop through and compare against the instances

Однако я должен сказать, что согласен с другими плакатами. Это пахнет ленивым и неэффективным. Вместо этого вы должны реализовать IComparable :-).

tsimon
источник
Я просто смотрел на IComparable, но мне показалось, что он предназначен для сортировки и упорядочивания ... действительно ли это полезно для сравнения равенства двух объектов?
nailitdown
Совершенно верно, потому что .Equals (объект o) определяется как this.CompareTo (o) == 0. Итак, equals использует ComparesTo () для определения равенства. Это будет намного эффективнее (и стандартная практика), чем использование отражения.
tsimon
Я могу ошибаться, полагая, что Equals реализован (или должен быть реализован) со ссылкой на CompareTo (). Вам следует рассмотреть возможность переопределения Equals, как описано здесь: stackoverflow.com/questions/104158/…
tsimon
1

здесь пересмотренный для обработки null = null как равного

 private bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
        {
            if (self != null && to != null)
            {
                Type type = typeof(T);
                List<string> ignoreList = new List<string>(ignore);
                foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    if (!ignoreList.Contains(pi.Name))
                    {
                        object selfValue = type.GetProperty(pi.Name).GetValue(self, null);
                        object toValue = type.GetProperty(pi.Name).GetValue(to, null);
                        if (selfValue != null)
                        {
                            if (!selfValue.Equals(toValue))
                                return false;
                        }
                        else if (toValue != null)
                            return false;
                    }
                }
                return true;
            }
            return self == to;
        }
Хоссейн
источник
Что, если бы у меня был глубокий граф объектов, какой из приведенных выше способов лучше всего использовать для возврата списка старых и новых свойств, которые были изменены?
Rod
1

В итоге я сделал это:

    public static string ToStringNullSafe(this object obj)
    {
        return obj != null ? obj.ToString() : String.Empty;
    }
    public static bool Compare<T>(T a, T b)
    {
        int count = a.GetType().GetProperties().Count();
        string aa, bb;
        for (int i = 0; i < count; i++)
        {
            aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe();
            bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe();
            if (aa != bb)
            {
                return false;
            }
        }
        return true;
    }

Использование:

    if (Compare<ObjectType>(a, b))

Обновить

Если вы хотите игнорировать некоторые свойства по имени:

    public static string ToStringNullSafe(this object obj)
    {
        return obj != null ? obj.ToString() : String.Empty;
    }
    public static bool Compare<T>(T a, T b, params string[] ignore)
    {
        int count = a.GetType().GetProperties().Count();
        string aa, bb;
        for (int i = 0; i < count; i++)
        {
            aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe();
            bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe();
            if (aa != bb && ignore.Where(x => x == a.GetType().GetProperties()[i].Name).Count() == 0)
            {
                return false;
            }
        }
        return true;
    }

Использование:

    if (MyFunction.Compare<ObjType>(a, b, "Id","AnotherProp"))
BjarkeCK
источник
1

Вы можете оптимизировать свой код, вызывая GetProperties только один раз для каждого типа:

public static string ToStringNullSafe(this object obj)
{
    return obj != null ? obj.ToString() : String.Empty;
}
public static bool Compare<T>(T a, T b, params string[] ignore)
{
    var aProps = a.GetType().GetProperties();
    var bProps = b.GetType().GetProperties();
    int count = aProps.Count();
    string aa, bb;
    for (int i = 0; i < count; i++)
    {
        aa = aProps[i].GetValue(a, null).ToStringNullSafe();
        bb = bProps[i].GetValue(b, null).ToStringNullSafe();
        if (aa != bb && ignore.Where(x => x == aProps[i].Name).Count() == 0)
        {
            return false;
        }
    }
    return true;
}
Моти Эльбилья
источник
1

Для полноты я хочу добавить ссылку на http://www.cyotek.com/blog/comparing-the-properties-of-two-objects-via-reflection Он имеет более полную логику, чем большинство других ответов на этой странице.

Однако я предпочитаю Compare-Net-объектов библиотеки https://github.com/GregFinzer/Compare-Net-Objects (далее по Ливиу Trifoi «s ответ )
Библиотека имеет пакет NuGet http://www.nuget.org/packages/ CompareNETObjects и несколько параметров для настройки.

Майкл Фрейджейм
источник
1

Убедитесь, что предметов нет null.

Имея obj1и obj2:

if(obj1 == null )
{
   return false;
}
return obj1.Equals( obj2 );
Рик Токио
источник
что, если они оба равны нулю? разве они не равны?
mmr 03
Хорошая точка зрения на нули, в моем случае использование .Equals (), похоже, не работает, поэтому я придумал это решение
nailitdown
ну, случай, который я тестирую, - это два объекта, один недавно созданный, один из сеанса. сравнение двух с .Equals () возвращает false, даже если оба имеют одинаковые значения свойств
nailitdown
0

Это работает, даже если объекты разные. вы можете настроить методы в классе утилит, возможно, вы также захотите сравнить частные свойства ...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

class ObjectA
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
    public string PropertyC { get; set; }
    public DateTime PropertyD { get; set; }

    public string FieldA;
    public DateTime FieldB;
}

class ObjectB
{
    public string PropertyA { get; set; }
    public string PropertyB { get; set; }
    public string PropertyC { get; set; }
    public DateTime PropertyD { get; set; }


    public string FieldA;
    public DateTime FieldB;


}

class Program
{
    static void Main(string[] args)
    {
        // create two objects with same properties
        ObjectA a = new ObjectA() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" };
        ObjectB b = new ObjectB() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" };

        // add fields to those objects
        a.FieldA = "hello";
        b.FieldA = "Something differnt";

        if (a.ComparePropertiesTo(b))
        {
            Console.WriteLine("objects have the same properties");
        }
        else
        {
            Console.WriteLine("objects have diferent properties!");
        }


        if (a.CompareFieldsTo(b))
        {
            Console.WriteLine("objects have the same Fields");
        }
        else
        {
            Console.WriteLine("objects have diferent Fields!");
        }

        Console.Read();
    }
}

public static class Utilities
{
    public static bool ComparePropertiesTo(this Object a, Object b)
    {
        System.Reflection.PropertyInfo[] properties = a.GetType().GetProperties(); // get all the properties of object a

        foreach (var property in properties)
        {
            var propertyName = property.Name;

            var aValue = a.GetType().GetProperty(propertyName).GetValue(a, null);
            object bValue;

            try // try to get the same property from object b. maybe that property does
                // not exist! 
            {
                bValue = b.GetType().GetProperty(propertyName).GetValue(b, null);
            }
            catch
            {
                return false;
            }

            if (aValue == null && bValue == null)
                continue;

            if (aValue == null && bValue != null)
                return false;

            if (aValue != null && bValue == null)
               return false;

            // if properties do not match return false
            if (aValue.GetHashCode() != bValue.GetHashCode())
            {
                return false;
            }
        }

        return true;
    }



    public static bool CompareFieldsTo(this Object a, Object b)
    {
        System.Reflection.FieldInfo[] fields = a.GetType().GetFields(); // get all the properties of object a

        foreach (var field in fields)
        {
            var fieldName = field.Name;

            var aValue = a.GetType().GetField(fieldName).GetValue(a);

            object bValue;

            try // try to get the same property from object b. maybe that property does
            // not exist! 
            {
                bValue = b.GetType().GetField(fieldName).GetValue(b);
            }
            catch
            {
                return false;
            }

            if (aValue == null && bValue == null)
               continue;

            if (aValue == null && bValue != null)
               return false;

            if (aValue != null && bValue == null)
               return false;


            // if properties do not match return false
            if (aValue.GetHashCode() != bValue.GetHashCode())
            {
                return false;
            }
        }

        return true;
    }


}
Тоно Нам
источник
Этот код не на 100% эффективен. он не работает в некоторых ситуациях, например, если он содержит свойство типа object.
Тоно Нам
0

Обновление ответа Ливиу выше - CompareObjects.DifferencesString устарело.

Это хорошо работает в модульном тесте:

CompareLogic compareLogic = new CompareLogic();
ComparisonResult result = compareLogic.Compare(object1, object2);
Assert.IsTrue(result.AreEqual);
Даниэль де Цваан
источник
1
Замечательно, что вы исправили искажение, но я думаю, что этот ответ должен быть комментарием в ответе Ливиу. Тем более, что в вашем примере кода (по сравнению с Liviu) отсутствуют параметры CompareLogic (которые, я уверен, важны), а также сообщение assert (которое было устаревшим). Утверждение можно исправить с помощью:Assert.IsTrue(result.AreEqual, result.DifferencesString);
Мариано Десанце
0

Этот метод получит propertiesиз класса и сравнит значения для каждого property. Если какие-либо значения отличаются, будет return false, иначе будет return true.

public static bool Compare<T>(T Object1, T object2)
{
    //Get the type of the object
    Type type = typeof(T);

    //return false if any of the object is false
    if (Object1 == null || object2 == null)
        return false;

    //Loop through each properties inside class and get values for the property from both the objects and compare
    foreach (System.Reflection.PropertyInfo property in type.GetProperties())
    {
        if (property.Name != "ExtensionData")
        {
            string Object1Value = string.Empty;
            string Object2Value = string.Empty;
            if (type.GetProperty(property.Name).GetValue(Object1, null) != null)
                Object1Value = type.GetProperty(property.Name).GetValue(Object1, null).ToString();
            if (type.GetProperty(property.Name).GetValue(object2, null) != null)
                Object2Value = type.GetProperty(property.Name).GetValue(object2, null).ToString();
            if (Object1Value.Trim() != Object2Value.Trim())
            {
                return false;
            }
        }
    }
    return true;
}

Использование:

bool isEqual = Compare<Employee>(Object1, Object2)

отправитель
источник
0

Чтобы расширить ответ @nawfal: s, я использую его для тестирования объектов разных типов в своих модульных тестах для сравнения одинаковых имен свойств. В моем случае объект базы данных и DTO.

Используется так в моем тесте;

Assert.IsTrue(resultDto.PublicInstancePropertiesEqual(expectedEntity));



public static bool PublicInstancePropertiesEqual<T, Z>(this T self, Z to, params string[] ignore) where T : class
{
    if (self != null && to != null)
    {
        var type = typeof(T);
        var type2 = typeof(Z);
        var ignoreList = new List<string>(ignore);
        var unequalProperties =
           from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
           where !ignoreList.Contains(pi.Name)
           let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
           let toValue = type2.GetProperty(pi.Name).GetValue(to, null)
           where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
           select selfValue;
           return !unequalProperties.Any();
    }
    return self == null && to == null;
}
Sgedda
источник
0

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

public abstract class ValueObject<T> where T : ValueObject<T>
{
    protected abstract IEnumerable<object> GetAttributesToIncludeInEqualityCheck();

    public override bool Equals(object other)
    {
        return Equals(other as T);
    }

    public bool Equals(T other)
    {
        if (other == null)
        {
            return false;
        }

        return GetAttributesToIncludeInEqualityCheck()
            .SequenceEqual(other.GetAttributesToIncludeInEqualityCheck());
    }

    public static bool operator ==(ValueObject<T> left, ValueObject<T> right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(ValueObject<T> left, ValueObject<T> right)
    {
        return !(left == right);
    }

    public override int GetHashCode()
    {
        int hash = 17;
        foreach (var obj in this.GetAttributesToIncludeInEqualityCheck())
            hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());

        return hash;
    }
}

и позже используйте этот абстрактный класс для сравнения объектов

public class Meters : ValueObject<Meters>
{
    ...

    protected decimal DistanceInMeters { get; private set; }

    ...

    protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
    {
        return new List<Object> { DistanceInMeters };
    }
}
Мистер Тыква
источник
0

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

    public static IEnumerable<ObjectPropertyChanged> GetPublicSimplePropertiesChanged<T>(this T previous, T proposedChange,
     string[] namesOfPropertiesToBeIgnored) where T : class
    {
        return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, true, null, null);
    }

    public static IReadOnlyList<ObjectPropertyChanged> GetPublicGenericPropertiesChanged<T>(this T previous, T proposedChange,
        string[] namesOfPropertiesToBeIgnored) where T : class
    {
        return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, false, null, null);
    }

    /// <summary>
    /// Gets the names of the public properties which values differs between first and second objects.
    /// Considers 'simple' properties AND for complex properties without index, get the simple properties of the children objects.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="previous">The previous object.</param>
    /// <param name="proposedChange">The second object which should be the new one.</param>
    /// <param name="namesOfPropertiesToBeIgnored">The names of the properties to be ignored.</param>
    /// <param name="simpleTypeOnly">if set to <c>true</c> consider simple types only.</param>
    /// <param name="parentTypeString">The parent type string. Meant only for recursive call with simpleTypeOnly set to <c>true</c>.</param>
    /// <param name="secondType">when calling recursively, the current type of T must be clearly defined here, as T will be more generic (using base class).</param>
    /// <returns>
    /// the names of the properties
    /// </returns>
    private static IReadOnlyList<ObjectPropertyChanged> GetPublicGenericPropertiesChanged<T>(this T previous, T proposedChange,
        string[] namesOfPropertiesToBeIgnored, bool simpleTypeOnly, string parentTypeString, Type secondType) where T : class
    {
        List<ObjectPropertyChanged> propertiesChanged = new List<ObjectPropertyChanged>();

        if (previous != null && proposedChange != null)
        {
            var type = secondType == null ? typeof(T) : secondType;
            string typeStr = parentTypeString + type.Name + ".";
            var ignoreList = namesOfPropertiesToBeIgnored.CreateList();
            IEnumerable<IEnumerable<ObjectPropertyChanged>> genericPropertiesChanged =
                from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                where !ignoreList.Contains(pi.Name) && pi.GetIndexParameters().Length == 0 
                    && (!simpleTypeOnly || simpleTypeOnly && pi.PropertyType.IsSimpleType())
                let firstValue = type.GetProperty(pi.Name).GetValue(previous, null)
                let secondValue = type.GetProperty(pi.Name).GetValue(proposedChange, null)
                where firstValue != secondValue && (firstValue == null || !firstValue.Equals(secondValue))
                let subPropertiesChanged = simpleTypeOnly || pi.PropertyType.IsSimpleType()
                    ? null
                    : GetPublicGenericPropertiesChanged(firstValue, secondValue, namesOfPropertiesToBeIgnored, true, typeStr, pi.PropertyType)
                let objectPropertiesChanged = subPropertiesChanged != null && subPropertiesChanged.Count() > 0
                    ? subPropertiesChanged
                    : (new ObjectPropertyChanged(proposedChange.ToString(), typeStr + pi.Name, firstValue.ToStringOrNull(), secondValue.ToStringOrNull())).CreateList()
                select objectPropertiesChanged;

            if (genericPropertiesChanged != null)
            {   // get items from sub lists
                genericPropertiesChanged.ForEach(a => propertiesChanged.AddRange(a));
            }
        }
        return propertiesChanged;
    }

Использование следующего класса для хранения результатов сравнения

[System.Serializable]
public class ObjectPropertyChanged
{
    public ObjectPropertyChanged(string objectId, string propertyName, string previousValue, string changedValue)
    {
        ObjectId = objectId;
        PropertyName = propertyName;
        PreviousValue = previousValue;
        ProposedChangedValue = changedValue;
    }

    public string ObjectId { get; set; }

    public string PropertyName { get; set; }

    public string PreviousValue { get; set; }

    public string ProposedChangedValue { get; set; }
}

И образец модульного теста:

    [TestMethod()]
    public void GetPublicGenericPropertiesChangedTest1()
    {
        // Define objects to test
        Function func1 = new Function { Id = 1, Description = "func1" };
        Function func2 = new Function { Id = 2, Description = "func2" };
        FunctionAssignment funcAss1 = new FunctionAssignment
        {
            Function = func1,
            Level = 1
        };
        FunctionAssignment funcAss2 = new FunctionAssignment
        {
            Function = func2,
            Level = 2
        };

        // Main test: read properties changed
        var propertiesChanged = Utils.GetPublicGenericPropertiesChanged(funcAss1, funcAss2, null);

        Assert.IsNotNull(propertiesChanged);
        Assert.IsTrue(propertiesChanged.Count == 3);
        Assert.IsTrue(propertiesChanged[0].PropertyName == "FunctionAssignment.Function.Description");
        Assert.IsTrue(propertiesChanged[1].PropertyName == "FunctionAssignment.Function.Id");
        Assert.IsTrue(propertiesChanged[2].PropertyName == "FunctionAssignment.Level");
    }
EricBDev
источник