C # 'de nesne özelliklerini karşılaştırma [kapalı]


111

Bu, diğer sınıflarımın çoğu tarafından miras alınan bir sınıf üzerinde bir yöntem olarak ortaya çıkardığım şeydir. Buradaki fikir, aynı Türdeki Nesnelerin özellikleri arasında basit karşılaştırmaya izin vermesidir.

Şimdi, bu işe yarıyor - ancak kodumun kalitesini iyileştirmek için onu incelemeye atacağımı düşündüm. Nasıl daha iyi / daha verimli vb. Olabilir?

/// <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
Bu arada, bu SE sitesinin farkında mısınız: codereview.stackexchange.com
wip

Birkaç nesne karşılaştırma kitaplığı var: CompareNETObjects , DeepEqual , AutoCompare , ZCompare ...
nawfal

... ve bunlardan bazıları jenerik eşitlik karşılaştırıcısı gerçeklenimcileri bir ton: MemberwiseEqualityComparer , Eşit , SemanticComparison , EqualityComparer , DeepEqualityComparer , Eşitlik , Equals.Fody . İkinci grup, neyi başarabilecekleri konusunda kapsam ve esneklik açısından sınırlı olabilir.
nawfal

Yanıtlar:


160

Birim testi yazarken yardımcı olmaya benzer bir şey yapacak bir kod parçası arıyordum. İşte kullanmaya son verdiğim şey.

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

DÜZENLE:

Yukarıdaki ile aynı kod ancak LINQ ve Extension yöntemlerini kullanır:

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 - oldukça yaşlı, ama kesinlikle hem test hem de basit karşılaştırmalar için harika bir amaca hizmet ediyor .. teşekkürler +1
jim tollan

1
Bu iyi, ancak daha karmaşık nesnelerle çalışmadığını buldum. Örneğin, bazı dizeleri olan bir nesnem var (onları iyi karşılaştırıyor) ama sonra bu nesnede başka bir nesnenin de doğru bir şekilde karşılaştırılamadığı bir listesi var, bu yüzden bunu bir şekilde yinelemem gerekiyor.
Ryan Thomas

1
İlk olarak iki kriter daha eklemek zorunda kaldım çünkü diğer durumda istisna oluşturan indekslenmiş özellikleri hariç tutmanız gerekiyor. İşte bu hatanın kriterleri: pi.GetIndexParameters (). Length == 0. Ve @RyanThomas tarafından belirtilen sorunu çözmek için ikinci kriter şudur: pi.GetUnderlyingType (). IsSimpleType (). Göreceğiniz gibi, IsSimpleType, Type sınıfı için mevcut olmayan bir uzantıdır. Tüm bu koşulları ve uzantıyı eklemek için yanıtı değiştirdim.
Samuel

64

GÜNCELLEME: Compare-Net-Objects'in en son sürümü GitHub'da bulunur , NuGet paketine ve Öğreticiye sahiptir . Gibi çağrılabilir

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

Veya bazı yapılandırmaları değiştirmeniz gerekirse,

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

Yapılandırılabilir parametrelerin tam listesi ComparisonConfig.cs içindedir.

Orijinal cevap:

Kodunuzda gördüğüm sınırlamalar:

  • En büyüğü, derin bir nesne karşılaştırması yapmamasıdır.

  • Özelliklerin liste olması veya öğe olarak liste içermesi durumunda (bu, n düzeylerine gidebilir), öğe bazında karşılaştırma yapmaz.

  • Bazı özellik türlerinin karşılaştırılmaması gerektiği hesaba katılmaz (örneğin, PagedCollectionView sınıfındaki gibi filtreleme amacıyla kullanılan bir Func özelliği).

  • Hangi özelliklerin gerçekte farklı olduğunu takip etmez (böylece iddialarınızda gösterebilirsiniz).

Bugün, mülk derinlemesine karşılaştırmaya göre mülkiyet yapmak için birim testi amaçları için bazı çözümler arıyordum ve sonunda şunu kullandım: http://comparenetobjects.codeplex.com .

Bu, şu şekilde kullanabileceğiniz tek bir sınıftan oluşan ücretsiz bir kütüphanedir:

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

Ayrıca Silverlight için kolayca yeniden derlenebilir. Bir sınıfı bir Silverlight projesine kopyalayın ve özel üye karşılaştırması gibi Silverlight'ta bulunmayan karşılaştırmalar için bir veya iki satırlık kodu kaldırın.


2
Liviu, sınıfın Silverlight ile uyumlu olmadığı hakkındaki yorumunu fark ettim. Silverlight ve Windows Phone 7 ile uyumlu olacak şekilde değiştirdim. En yenisini yapın. En değişim seti 74131 Bkz comparenetobjects.codeplex.com/SourceControl/list/changesets
Greg Finzer

Bu umut verici görünüyor. Bunu deneyeceğim
DJ Burb

Harika örnek için teşekkürler! Ayrıca, IgnoreObjectTypesfarklı türler olduğunda ayar yapmak yararlı olabilir.
Sergey Brunov

Sürüm 2.0, Silverlight 5+, Windows Phone 8+, WinRT 8+, Xamarin IOS ve Xamarin Droid
Greg Finzer

DifferencesStringCompareObjects sınıfında kullanımdan kaldırıldı. Ama şimdi bunu ComparisonResult'tan alabilirsiniz:var r = compareObjects.Compare(objectA, objectB); Assert.IsTrue(r.AreEqual, r.DifferencesString);
Mariano Desanze

6

Daha iyi bir açıklama için Nesne # Eşittir () Geçersiz Kılma modelini takip etmenin en iyisi olacağını düşünüyorum
: Bill Wagner'in Etkili C # - Madde 9'u okuyun.

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);
}
  • Ayrıca eşitliği kontrol eden yöntemlerde, doğru veya yanlış döndürmelisiniz. ya eşitler ya da değiller .. bir istisna atmak yerine, yanlış döndür.
  • Object # Equals'ı geçersiz kılmayı düşünürdüm.
  • Bunu düşünmüş olman gerekse bile, özellikleri karşılaştırmak için Yansıma kullanmak sözde yavaştır (bunu destekleyecek numaralarım yok). Bu, C # 'da valueType # Eşittir için varsayılan davranıştır ve değer türleri için Eşittir'i geçersiz kılmanız ve performans için üye açısından bir karşılaştırma yapmanız önerilir. (Daha önce, özel Özellik nesneleri koleksiyonunuz olduğu için bunu hızlı okudum ... benim hatam.)

Güncelleme-Aralık 2011:

  • Elbette, türün zaten eşittir () üretimi varsa, başka bir yaklaşıma ihtiyacınız vardır.
  • Bunu, değişmez veri yapılarını yalnızca test amacıyla karşılaştırmak için kullanıyorsanız, üretim sınıflarına bir Eşittir eklememelisiniz (Birisi Eşittir uygulamasını zorlayarak testleri hortumlayabilir veya üretim için gerekli Eşittir uygulamasının oluşturulmasını engelleyebilirsiniz) .

Equals () öğesini geçersiz kılma ile ilgili sorunlarla karşılaştım çünkü bunu miras alınan bir temel sınıfta uygulamaya çalışıyorum ... çünkü bunun karşı çalıştırılacağı sınıfın anahtarlarını bilmiyorum, yapamam GetHasCode () için düzgün bir geçersiz kılma uygulayın (Equals () geçersiz kıldığınızda istenir).
nailitdown

Buradaki gereklilik, eğer objA.Equals (objB) ise, objA.GetHashCode () == objB.GetHashCode () olmasıdır. GetHashCode, bir sınıfın değişken durumuna / verilerine bağlı olmamalıdır ... Sınıf için anahtarlardan ne kastettiğinizi anlamadım .. Çözülebilecek bir şey gibi görünüyor. Temel türde 'anahtarlar' yok mu?
Gishu

6

Performans önemli değilse, bunları seri hale getirebilir ve sonuçları karşılaştırabilirsiniz:

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
bunu bir süre önce denediyseniz, kaç nesnenin seri hale getirilemeyeceğini merak edersiniz ...
Offler

5

Bence Big T'nin cevabı oldukça iyiydi ama derin karşılaştırma eksikti, bu yüzden biraz değiştirdim:

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

Kopyala ve yapıştır hatalarını önlemek için aşağıdaki satırı PublicInstancePropertiesEqual yöntemine eklerim:

Assert.AreNotSame(self, to);

2

Özelliklerdeki tüm nesnelerinizde .ToString () öğesini geçersiz kılıyor musunuz? Aksi takdirde, bu ikinci karşılaştırma null olarak geri gelebilir.

Ayrıca, bu ikinci karşılaştırmada, altı ay / iki yıl sonra okunabilirlik açısından (A! = B) 'ye kıyasla! (A == B)' nin yapısı konusunda kararsızım. Hattın kendisi oldukça geniştir; bu, geniş bir monitörünüz varsa sorun değil, ancak çok iyi çıktı alamayabilir. (kusur aramak)

Tüm nesneleriniz her zaman bu kodun çalışacağı özellikleri mi kullanıyor? Bir nesneden diğerine farklı olabilen, ancak açığa çıkan tüm veriler aynı olan bazı dahili, uygun olmayan veriler olabilir mi? Bir noktada aynı sayıya ulaşan ancak iki farklı bilgi dizisi üretecek olan iki rastgele sayı üreteci gibi zamanla değişebilecek bazı veriler veya sadece açığa çıkmayan herhangi bir veri düşünüyorum. özellik arayüzü üzerinden.


iyi puanlar -! = ... kabul edildi, puan alındı. ToString (), bir nesneyi döndüren .GetValue geçici çözüm girişimiydi (bu nedenle karşılaştırma her zaman yanlıştır, çünkü bu bir ref karşılaştırmasıdır) .. daha iyi bir yol var mı?
nailitdown

GetValue bir nesneyi döndürüyorsa, bu işlevi tekrar tekrar kullanabilir misiniz? yani, döndürülen nesnelerde PropertiesEqual'ı çağır?
mmr

1

Yalnızca aynı türdeki nesneleri veya miras zincirinin daha aşağısındaki nesneleri karşılaştırıyorsanız, neden parametreyi nesne yerine temel türünüz olarak belirtmiyorsunuz?

Ayrıca parametre üzerinde de boş kontroller yapın.

Ayrıca, kodu daha okunaklı hale getirmek için 'var'ı kullanırdım (eğer c # 3 kodu ise)

Ayrıca, nesnenin özellik olarak başvuru türleri varsa, değerleri gerçekten karşılaştırmayan bunlarda yalnızca ToString () çağırırsınız. ToString geçersiz kılınmamışsa, tür adını yanlış pozitifler döndürebilecek bir dizge olarak döndürecektir.


referans türleri hakkında iyi bir nokta - benim durumumda önemli değil ama iyi bir şans var.
nailitdown

1

Önereceğim ilk şey, gerçek karşılaştırmayı biraz daha okunaklı olacak şekilde bölmek olacaktır (ayrıca ToString () 'i de çıkardım - bu gerekli mi?):

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

    if (originalProperty != comparisonProperty)
        return false;

Bir sonraki öneri, yansıtma kullanımını mümkün olduğunca en aza indirmek olacaktır - gerçekten yavaştır. Yani gerçekten yavaş. Bunu yapacaksanız, özellik referanslarını önbelleğe almanızı öneririm. Reflection API'ye çok yakından aşina değilim, bu yüzden eğer bu biraz kapalıysa, derlemek için ayarlama yapın:

// 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

Ancak diğer afişlere de katıldığımı söylemeliyim. Bu tembel ve verimsiz kokuyor. Bunun yerine IComparable'ı uygulamalısınız :-).


Sadece IComparable'a bakıyordum ama sanki sıralama ve sıralama içinmiş gibi görünüyordu .. iki nesnenin eşitliğini karşılaştırmak için gerçekten yararlı mı?
nailitdown

Kesinlikle, çünkü .Equals (nesne o) this.CompareTo (o) == 0 olarak tanımlanır. Dolayısıyla, equals eşitliği belirlemek için ComparesTo () kullanır. Bu, yansıma kullanmaktan çok daha verimli (ve standart uygulama) olacaktır.
tsimon

CompareTo () referans alınarak Eşitlerin uygulandığını (veya uygulanması gerektiğini) varsayıyor olabilirim.
Eşitleri

1

burada null = null eşit olarak ele alınacak şekilde revize edilmiştir

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

Ya derin bir nesne grafiğim olsaydı, değiştirilen eski ve yeni özelliklerin bir listesini döndürmek için yukarıda kullanmanın en iyi yolu nedir?
Çubuk

1

Bunu yaptım:

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

Kullanımı:

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

Güncelleme

Bazı özellikleri ada göre yok saymak istiyorsanız:

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

Kullanımı:

    if (MyFunction.Compare<ObjType>(a, b, "Id","AnotherProp"))

1

GetProperties'i her tür için yalnızca bir kez çağırarak kodunuzu optimize edebilirsiniz:

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

Tamlık için http://www.cyotek.com/blog/comparing-the-properties-of-two-objects-via-reflection referans eklemek istiyorum Bu sayfadaki diğer cevapların çoğundan daha eksiksiz bir mantığa sahiptir.

Ancak karşılaştırın-Net-Nesneleri tercih kütüphane https://github.com/GregFinzer/Compare-Net-Objects (tarafından yönlendirilen Liviu Trifoi 'ın cevabı )
kütüphane Nuget paketi vardır http://www.nuget.org/packages/ Yapılandırmak için NETObjects ve birden çok seçeneği karşılaştırın.


1

Nesnelerin olmadığından emin olun null.

Sahip olmak obj1ve obj2:

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

ya ikisi de boşsa? o zaman eşit değiller mi?
mmr

Benim durumumda .Equals () kullanmak işe yaramıyor, bu yüzden bu çözümü
buldum

Pekala, test ettiğim durum, biri yeni oluşturulmuş, biri oturumdan olan iki nesne. İkisini .Equals () ile karşılaştırmak, her ikisi de aynı özellik değerine sahip olsa bile yanlış döndürür
nailitdown

0

Bu, nesneler farklı olsa bile çalışır. yardımcı programlar sınıfındaki yöntemleri özelleştirebilirsiniz, belki özel özellikleri de karşılaştırmak isteyebilirsiniz ...

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


}

Bu kod% 100 verimli değil. bazı durumlarda, örneğin nesne türünün bir özelliğini içeriyorsa çalışmaz.
Tono Nam

0

Liviu'nun yukarıdaki cevabıyla ilgili güncelleme - CompareObjects.DifferencesString kullanımdan kaldırıldı.

Bu, bir birim testinde iyi çalışır:

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

1
Aşağılamayı düzelttiğiniz harika, ama bence bu yanıt Liviu'nun cevabında bir yorum olmalı. Özellikle, örnek kodunuz (Liviu ile karşılaştırıldığında) , CompareLogic parametrelerinden (ki eminim önemlidir) ve ayrıca iddia mesajından (kullanımdan kaldırılmış olan ) yoksun olduğu için. Assert.IsTrue(result.AreEqual, result.DifferencesString);
İddia

0

Bu yöntem propertiessınıfı alacak ve her birinin değerlerini karşılaştıracaktır property. Değerlerden herhangi biri farklıysa olur return false, yoksa olur 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;
}

Kullanımı:

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


0

@ Nawfal: ın yanıtını genişletmek için, bunu eşit özellik adlarını karşılaştırmak üzere birim testlerimde farklı türlerdeki nesneleri test etmek için kullanıyorum. Benim durumumda veritabanı varlığı ve DTO.

Testimde bu şekilde kullanıldı;

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

bazen tüm genel özellikleri karşılaştırmak istemezsiniz ve yalnızca alt kümelerini karşılaştırmak istersiniz, bu nedenle bu durumda, istenen özellikler listesini soyut sınıfla karşılaştırmak için mantığı taşıyabilirsiniz.

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

ve bu soyut sınıfı daha sonra nesneleri karşılaştırmak için kullanın

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

    protected decimal DistanceInMeters { get; private set; }

    ...

    protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
    {
        return new List<Object> { DistanceInMeters };
    }
}

0

Benim çözümüm yukarıdaki Aras Alenin cevabından esinlenerek, karşılaştırma sonuçları için bir seviye nesne karşılaştırması ve özel bir nesne ekledim. Nesne adına sahip özellik adını da almakla ilgileniyorum:

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

Karşılaştırma sonuçlarını depolamak için aşağıdaki sınıfı kullanma

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

Ve örnek bir birim testi:

    [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");
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.