Порівняння властивостей об'єкта в 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;
}


3
До речі ви в курсі цього сайту SE: codereview.stackexchange.com
WIP

Є кілька бібліотек порівняння об'єктів: ПорівнятиNETObjects , DeepEqual , AutoCompare , ZCompare ...
nawfal

... і багато спільних реалізаторів порівняння рівності, деякі з яких: MemberwiseEqualityComparer , Equ , SemanticComppare , 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
jim tollan

1
Це добре, але я виявив, що не працює зі складнішими об'єктами. Наприклад, у мене є об'єкт з деякими рядками (він порівнює їх добре), але тоді цей об’єкт також має список іншого об'єкта, який він не порівнює правильно, тому потрібно якось повторити це.
Райан Томас

1
Мені довелося додати критерії в першому, де ще два критерії, оскільки ви повинні виключити індексовані властивості, які викидають виключення в іншому випадку. Ось критерії для цієї помилки: pi.GetIndexParameters (). Довжина == 0. І другий критерій для вирішення проблеми, заявленої @RyanThomas, такий: pi.GetUnderlyingType (). IsSimpleType (). Як ви побачите, IsSimpleType є і розширення, які не існують для класу Type. Я змінив відповідь, щоб додати всі ці умови та розширення.
Самуїл

64

ОНОВЛЕННЯ: Остання версія Порівняння-Net-об'єктів розміщена на 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
   }
};

Повний список параметрів, що можна настроювати, розміщено у ComppareConfig.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
Грег Finzer

Це виглядає перспективно. Спробуй це
DJ Burb

Дякую за чудовий приклад! Крім того, IgnoreObjectTypesналаштування може бути корисним, коли існують різні типи.
Сергій Брунов

У версії 2.0 є версія бібліотеки портативних класів, сумісна з Silverlight 5+, Windows Phone 8+, WinRT 8+, Xamarin IOS та Xamarin Droid
Грег Фінзер

DifferencesStringбув застарілим у класі CompareObjects. Але тепер ви можете отримати це з Порівняння var r = compareObjects.Compare(objectA, objectB); Assert.IsTrue(r.AreEqual, r.DifferencesString);
Результату

6

Я думаю, що було б найкраще слідувати шаблону для переосмислення об'єкта # рівного ()
Для кращого опису: прочитайте Ефективний 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);
}
  • Також у методах, що перевіряють рівність, слід повернути або істинні, або хибні. або вони рівні, або вони не є .. замість того, щоб кидати виняток, поверніть помилкове.
  • Я вважаю, що переважаючий об’єкт # дорівнює.
  • Незважаючи на те, що ви, мабуть, це врахували, використання Reflection для порівняння властивостей нібито повільне (я не маю цифр, щоб підтвердити це). Це поведінка за замовчуванням для valueType # Equals в C #, і рекомендується переосмислювати рівність для типів значень і робити порівняння членів для продуктивності. (Раніше я з швидкістю читав це, оскільки у вас є колекція спеціальних об'єктів властивості ... моє погано.)

Оновлення-грудень 2011 року:

  • Звичайно, якщо тип вже має виробництво дорівнює (), то вам потрібен інший підхід.
  • Якщо ви використовуєте це для порівняння незмінних структур даних виключно для тестових цілей, ви не повинні додавати рівняння до виробничих класів (хтось може перенести шланги на тести, переслідуючи реалізацію рівних, або ви можете запобігти створенню необхідної для виробництва рівномірної реалізації) .

Я зіткнувся з проблемами з переважаючим. здійснити гідне переопрацювання для GetHasCode () (req'd, коли ви переймете рівний ()).
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
спробувавши це як-небудь назад, вам було б цікаво, скільки об’єктів не можна серіалізувати ...
Пропозиція

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

4

Я б додав наступний рядок до методу PublicInstancePropertiesEqual, щоб уникнути помилок копіювання та вставки:

Assert.AreNotSame(self, to);

2

Чи ви переосмислюєте .ToString () на всі ваші об’єкти, які є у властивостях? Інакше це друге порівняння може повернутися з нульовим.

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

Чи всі ваші об’єкти завжди використовують властивості такі, що цей код буде працювати? Чи можуть бути якісь внутрішні, неадекватні дані, які можуть відрізнятися від одного об’єкта до іншого, але всі відкриті дані однакові? Я думаю про деякі дані, які можуть змінитися з часом, як, наприклад, два генератори випадкових чисел, які в один момент потрапляють на одне число, але збираються створити дві різні послідовності інформації або просто будь-які дані, які не піддаються впливу через інтерфейс властивості.


хороші бали -! = ... погоджено, пункт взятий. ToString () була спробою вирішити .GetValue повернення об'єкта (таким чином порівняння завжди помилкове, оскільки це порівняльне порівняння) .. Чи є кращий спосіб?
nailitdown

Якщо GetValue повертає об’єкт, чи можете ви повторно повторити цю функцію? тобто викликати PropertiesEqual на повернених об'єктах?
ммр

1

Якщо ви порівнюєте лише об'єкти одного типу або далі по ланцюжку успадкування, чому б не вказати параметр як базовий тип, а не об’єкт?

Також зробіть нульову перевірку параметра.

Крім того, я б скористався "var" просто для того, щоб зробити код читабельнішим (якщо його код №3)

Крім того, якщо об’єкт має типи посилань як властивості, ви просто викликаєте на них ToString (), який насправді не порівнює значення. Якщо ToString не перезаписаний, він просто поверне ім'я типу у вигляді рядка, який може повернути помилкові позитиви.


хороший пункт щодо еталонних типів - в моєму випадку це не має значення, але є хороший шанс, що це буде.
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;

Наступною пропозицією було б максимально мінімізувати використання рефлексії - це дійсно повільно. Я маю на увазі, дуже повільно. Якщо ви збираєтеся це зробити, я б запропонував кешувати посилання на властивості. Я не дуже знайомий з 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 :-).


Я просто дивився на Ізрівнянний, але здавалося, що це було для сортування та впорядкування. Це справді корисно для порівняння рівності двох об'єктів?
nailitdown

Абсолютно тому, що .Equals (об'єкт o) визначається як це.CompareTo (o) == 0. Отже, для визначення рівності використовується рівняння ComparesTo (). Це буде набагато ефективніше (і стандартна практика), ніж використання рефлексії.
цимон

Можливо, я помиляюся, припускаючи, що рівне значення реалізовано (або має бути впроваджене) з посиланням на 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;
        }

Що робити, якщо у мене був глибокий об’єктний графік, який найкращий спосіб використовувати вище, щоб повернути список старих і нових властивостей, які були змінені?
Прут

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"))

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/ ПорівняйтеNETObjects та кілька варіантів налаштування.


1

Переконайтесь, що об’єкти - ні null.

Маючи obj1та obj2:

if(obj1 == null )
{
   return false;
}
return obj1.Equals( obj2 );

що робити, якщо вони обоє недійсні? чи не вони тоді рівні?
mmr

хороший пункт про нулі, в моєму випадку використання .Equals (), здається, не працює, тому я придумав це рішення
nailitdown

Що ж, тест на який я тестую - це два об'єкти, один новостворений, один із сеансу. порівнюючи два з .Equals () повертає помилкові, навіть якщо обидва мають однакові значення властивостей
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% ефективним. він не працює в деяких ситуаціях, наприклад, якщо він містить властивість об'єкта типу.
Тоно Нам

0

Оновлення відповіді Лівіу вище - CompareObjects.DifferencesString застаріло.

Це добре працює в одиничному тесті:

CompareLogic compareLogic = new CompareLogic();
ComparisonResult result = compareLogic.Compare(object1, object2);
Assert.IsTrue(result.AreEqual);

1
Чудово, що ви зафіксували депракацію, але я думаю, що ця відповідь насправді повинна бути коментарем у відповіді Лівіу. Тим більше, що у вашому зразковому коді (порівняно з Liviu) відсутні параметри CompareLogic (які я впевнені, що важливі), а також повідомлення про затвердження (яке було застарілим). 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;
}

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");
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.