Membandingkan properti objek di c # [closed]


111

Inilah yang saya temukan sebagai metode pada kelas yang diwarisi oleh banyak kelas saya yang lain. Idenya adalah memungkinkan perbandingan sederhana antara properti Objek dari Jenis yang sama.

Sekarang, ini berhasil - tetapi untuk meningkatkan kualitas kode saya, saya pikir saya akan membuangnya untuk diperiksa. Bagaimana bisa lebih baik / lebih efisien / dll.?

/// <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
Ngomong-ngomong, apakah Anda mengetahui situs SE ini: codereview.stackexchange.com
wip

Ada beberapa pustaka perbandingan objek: CompareNETObjects , DeepEqual , AutoCompare , ZCompare ...
nawfal

... dan satu ton pelaksana kesetaraan comparer generik, beberapa di antaranya adalah: MemberwiseEqualityComparer , equ , SemanticComparison , EqualityComparer , DeepEqualityComparer , Kesetaraan , Equals.Fody . Kelompok terakhir mungkin terbatas dalam ruang lingkup dan fleksibilitas mengenai apa yang dapat mereka capai.
nawfal

Saya memberikan suara untuk menutup pertanyaan ini sebagai di luar topik karena ini milik tinjauan kode
Xiaoy312

Jawaban:


160

Saya sedang mencari potongan kode yang akan melakukan sesuatu yang mirip untuk membantu menulis uji unit. Inilah yang akhirnya saya gunakan.

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

EDIT:

Kode yang sama seperti di atas tetapi menggunakan metode LINQ dan Ekstensi:

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 - cukup kuno, tapi pasti memiliki tujuan yang bagus untuk pengujian dan perbandingan sederhana .. terima kasih +1
jim tollan

1
Ini bagus, tetapi menurut saya tidak berfungsi dengan objek yang lebih kompleks. Misalnya saya memiliki objek dengan beberapa string (membandingkannya dengan baik) tetapi kemudian objek ini juga memiliki daftar objek lain, yang tidak dapat dibandingkan dengan benar, jadi perlu mengulanginya.
Ryan Thomas

1
Saya harus menambahkan ke kriteria pertama di mana dua kriteria lagi karena Anda harus mengecualikan properti yang diindeks yang membuang pengecualian dalam kasus lain. Berikut adalah kriteria untuk error ini: pi.GetIndexParameters (). Length == 0. Dan kriteria kedua untuk menyelesaikan masalah yang dinyatakan oleh @RyanThomas adalah: pi.GetUnderlyingType (). IsSimpleType (). Seperti yang akan Anda lihat, IsSimpleType adalah dan ekstensi yang tidak ada untuk Jenis kelas. Saya mengubah jawaban untuk menambahkan semua ketentuan ini dan ekstensi.
Samuel

64

UPDATE: Versi terbaru Compare-Net-Objects terletak di GitHub , memiliki paket NuGet dan Tutorial . Bisa disebut suka

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

Atau jika Anda perlu mengubah beberapa konfigurasi, gunakan

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

Daftar lengkap parameter yang dapat dikonfigurasi ada di ComparisonConfig.cs

Jawaban asli:

Batasan yang saya lihat di kode Anda:

  • Yang terbesar adalah ia tidak melakukan perbandingan objek yang dalam.

  • Itu tidak melakukan perbandingan elemen dengan elemen dalam kasus properti adalah daftar atau berisi daftar sebagai elemen (ini bisa menjadi n-level).

  • Ini tidak memperhitungkan bahwa beberapa jenis properti tidak boleh dibandingkan (misalnya, properti Func yang digunakan untuk tujuan pemfilteran, seperti yang ada di kelas PagedCollectionView).

  • Itu tidak melacak properti apa yang sebenarnya berbeda (sehingga Anda dapat menampilkan dalam pernyataan Anda).

Hari ini saya mencari beberapa solusi untuk tujuan pengujian unit untuk melakukan perbandingan mendalam properti dengan properti dan saya akhirnya menggunakan: http://comparenetobjects.codeplex.com .

Ini adalah perpustakaan gratis dengan hanya satu kelas yang dapat Anda gunakan seperti ini:

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

Juga, dapat dengan mudah dikompilasi ulang untuk Silverlight. Cukup salin satu kelas ke dalam proyek Silverlight dan hapus satu atau dua baris kode untuk perbandingan yang tidak tersedia di Silverlight, seperti perbandingan anggota pribadi.


2
Liviu, saya perhatikan komentar Anda tentang kelas yang tidak kompatibel dengan Silverlight. Saya baru saja mengubahnya agar kompatibel dengan Silverlight dan Windows Phone 7. Dapatkan yang terbaru. Lihat set perubahan 74131 di comparenetobjects.codeplex.com/SourceControl/list/changesets
Greg Finzer

Ini terlihat menjanjikan. Akan mencobanya
DJ Burb

Terima kasih atas contoh yang bagus! Selain itu, IgnoreObjectTypespengaturan mungkin berguna bila ada jenis yang berbeda.
Sergey Brunov

Versi 2.0 memiliki versi Perpustakaan Kelas Portabel yang kompatibel dengan Silverlight 5+, Windows Phone 8+, WinRT 8+, Xamarin IOS dan Xamarin Droid
Greg Finzer

DifferencesStringtelah dikoreksi di kelas CompareObjects. Tapi sekarang Anda bisa mendapatkannya dari ComparisonResult sebagai gantinya:var r = compareObjects.Compare(objectA, objectB); Assert.IsTrue(r.AreEqual, r.DifferencesString);
Mariano Desanze

6

Saya pikir akan lebih baik untuk mengikuti pola untuk Override Object # Equals ()
Untuk penjelasan yang lebih baik: Baca C # Efektif Bill Wagner - Item 9 menurut saya

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);
}
  • Juga dalam metode yang memeriksa kesetaraan, Anda harus mengembalikan benar atau salah. apakah mereka sama atau tidak .. alih-alih memberikan pengecualian, kembalikan false.
  • Saya akan mempertimbangkan untuk mengganti Object # Equals.
  • Meskipun Anda pasti telah mempertimbangkan ini, menggunakan Refleksi untuk membandingkan properti dianggap lambat (saya tidak memiliki angka untuk mendukungnya). Ini adalah perilaku default untuk valueType # Sama dengan di C # dan Anda disarankan untuk mengganti Sama dengan untuk tipe nilai dan melakukan perbandingan yang bijak bagi kinerja anggota. (Sebelumnya saya membaca cepat ini karena Anda memiliki koleksi objek Properti khusus ... salah saya.)

Pembaruan-Des 2011:

  • Tentu saja, jika tipe sudah memiliki produksi Equals () maka Anda memerlukan pendekatan lain.
  • Jika Anda menggunakan ini untuk membandingkan struktur data yang tidak dapat diubah secara eksklusif untuk tujuan pengujian, Anda tidak boleh menambahkan Equals to production class (Seseorang mungkin menyela pengujian dengan mengubah implementasi Equals atau Anda dapat mencegah pembuatan implementasi Equals yang diperlukan produksi) .

Saya mengalami masalah dengan overriding .Equals () karena saya mencoba menerapkan ini pada kelas dasar yang diwarisi ... karena saya tidak tahu kunci untuk kelas yang akan dijalankan ini, saya tidak bisa mengimplementasikan penggantian yang layak untuk GetHasCode () (diminta saat Anda mengganti Equals ()).
nailitdown

Persyaratannya adalah jika objA.Equals (objB) maka objA.GetHashCode () == objB.GetHashCode (). GetHashCode seharusnya tidak bergantung pada status / data kelas yang bisa berubah ... Saya tidak mengerti apa yang Anda maksud dengan kunci untuk kelas .. Sepertinya sesuatu yang bisa diselesaikan. Bukankah tipe dasar memiliki 'kunci'?
Gishu

6

Jika kinerja tidak penting, Anda dapat membuat serial dan membandingkan hasilnya:

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
mencoba ini beberapa saat yang lalu, Anda akan bertanya-tanya berapa banyak objek yang tidak dapat diserialkan ...
Offler

5

Saya pikir jawaban Big T cukup bagus tetapi perbandingan yang dalam tidak ada, jadi saya mengubahnya sedikit:

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

Saya akan menambahkan baris berikut ke metode PublicInstancePropertiesEqual untuk menghindari kesalahan salin & tempel:

Assert.AreNotSame(self, to);

2

Apakah Anda mengganti .ToString () pada semua objek Anda yang ada di properti? Jika tidak, perbandingan kedua itu bisa menghasilkan nol.

Juga, dalam perbandingan kedua itu, saya ragu tentang konstruksi! (A == B) dibandingkan dengan (A! = B), dalam hal keterbacaan enam bulan / dua tahun dari sekarang. Garisnya sendiri cukup lebar, yang tidak masalah jika Anda memiliki monitor yang lebar, tetapi mungkin tidak dapat mencetak dengan baik. (nitpick)

Apakah semua objek Anda selalu menggunakan properti sehingga kode ini akan berfungsi? Mungkinkah ada beberapa data internal non-properti yang dapat berbeda dari satu objek ke objek lainnya, tetapi semua data yang terekspos sama? Saya memikirkan beberapa data yang dapat berubah seiring waktu, seperti dua generator angka acak yang kebetulan mengenai nomor yang sama pada satu titik, tetapi akan menghasilkan dua urutan informasi yang berbeda, atau hanya data apa pun yang tidak terekspos. melalui antarmuka properti.


poin bagus -! = ... setuju, poin diambil. ToString () adalah upaya untuk menyelesaikan .GetValue mengembalikan objek (sehingga perbandingannya selalu salah, karena ini adalah perbandingan referensi) .. adakah cara yang lebih baik?
nailitdown

Jika GetValue mengembalikan sebuah objek, dapatkah Anda mengulangi fungsi ini lagi? yaitu, panggil PropertiesEqual pada objek yang dikembalikan?
mmr

1

Jika Anda hanya membandingkan objek dengan tipe yang sama atau lebih jauh di rantai pewarisan, mengapa tidak menentukan parameter sebagai tipe dasar Anda, daripada objek?

Juga lakukan pemeriksaan nol pada parameter juga.

Selanjutnya saya akan menggunakan 'var' hanya untuk membuat kode lebih mudah dibaca (jika kode c # 3)

Selain itu, jika objek memiliki tipe referensi sebagai properti maka Anda hanya memanggil ToString () padanya yang tidak benar-benar membandingkan nilai. Jika ToString tidak diganti maka itu hanya akan mengembalikan nama tipe sebagai string yang dapat mengembalikan positif palsu.


poin bagus tentang jenis referensi - dalam kasus saya itu tidak masalah tetapi ada kemungkinan besar itu akan terjadi.
nailitdown

1

Hal pertama yang saya sarankan adalah membagi perbandingan sebenarnya sehingga sedikit lebih mudah dibaca (saya juga telah mengeluarkan ToString () - apakah itu diperlukan?):

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

    if (originalProperty != comparisonProperty)
        return false;

Saran selanjutnya adalah meminimalkan penggunaan refleksi sebanyak mungkin - ini sangat lambat. Maksudku, sangat lambat. Jika Anda akan melakukan ini, saya sarankan untuk menyimpan referensi properti ke cache. Saya tidak terlalu familiar dengan Reflection API, jadi jika ini sedikit salah, sesuaikan saja untuk membuatnya dikompilasi:

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

Namun, saya harus mengatakan bahwa saya setuju dengan poster lainnya. Ini berbau malas dan tidak efisien. Anda harus mengimplementasikan IComparable sebagai gantinya :-).


Saya baru saja melihat IComparable tetapi sepertinya untuk menyortir dan memesan .. apakah ini benar-benar berguna untuk membandingkan kesetaraan dua objek?
nailitdown

Tentu saja, karena .Equals (objek o) didefinisikan sebagai this.CompareTo (o) == 0. Jadi, persamaan menggunakan ComparesTo () untuk menentukan persamaan. Ini akan jauh lebih efisien (dan praktik standar) daripada menggunakan refleksi.
tsimon

Saya mungkin salah dengan asumsi bahwa Equals diimplementasikan (atau harus diimplementasikan) dengan mengacu pada CompareTo (). Anda harus mempertimbangkan untuk mengganti Sama seperti yang dijelaskan di sini: stackoverflow.com/questions/104158/…
tsimon

1

di sini direvisi satu untuk memperlakukan null = null sebagai sama

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

Bagaimana jika saya memiliki grafik objek dalam, apa cara terbaik yang digunakan di atas untuk mengembalikan daftar properti lama dan baru yang diubah?
Batang

1

Saya akhirnya melakukan ini:

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

Pemakaian:

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

Memperbarui

Jika Anda ingin mengabaikan beberapa properti dengan nama:

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

Pemakaian:

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

1

Anda dapat mengoptimalkan kode Anda dengan memanggil GetProperties hanya sekali per jenis:

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

Untuk kelengkapan saya ingin menambahkan referensi ke http://www.cyotek.com/blog/comparing-the-properties-of-two-objects-via-reflection Ini memiliki logika yang lebih lengkap daripada kebanyakan jawaban lain di halaman ini.

Namun saya lebih suka Bandingkan-Net-Objects perpustakaan https://github.com/GregFinzer/Compare-Net-Objects (disebut oleh Liviu Trifoi 's jawaban )
Perpustakaan memiliki nuget paket http://www.nuget.org/packages/ BandingkanNETObjects dan beberapa opsi untuk dikonfigurasi.


1

Pastikan objek tidak null .

Memiliki obj1dan obj2:

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

bagaimana jika keduanya nol? Bukankah mereka sama?
mmr

poin bagus pada nulls, dalam kasus saya menggunakan .Equals () tampaknya tidak berfungsi, itulah sebabnya saya datang dengan solusi ini
nailitdown

Nah, kasus yang saya uji adalah dua objek, satu baru dibuat, satu dari sesi. membandingkan keduanya dengan .Equals () mengembalikan false meskipun keduanya memiliki nilai properti yang identik
nailitdown

0

Ini berfungsi bahkan jika objeknya berbeda. Anda dapat menyesuaikan metode di kelas utilitas mungkin Anda ingin membandingkan properti pribadi juga ...

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


}

Kode itu tidak 100% efisien. itu tidak bekerja dalam beberapa situasi misalnya jika itu berisi properti tipe objek.
Tono Nam

0

Pembaruan atas jawaban Liviu di atas - CompareObjects.DifferencesString sudah tidak digunakan lagi.

Ini bekerja dengan baik dalam pengujian unit:

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

1
Sangat bagus bahwa Anda memperbaiki depracation tersebut, tetapi saya pikir jawaban ini seharusnya menjadi komentar dalam jawaban Liviu. Terutama karena kode sampel Anda (dibandingkan dengan Liviu) tidak memiliki parameter CompareLogic (yang saya yakin penting), dan juga pesan assert (yang sudah usang). Pernyataan tersebut dapat diperbaiki dengan:Assert.IsTrue(result.AreEqual, result.DifferencesString);
Mariano Desanze

0

Metode ini akan mendapatkan propertieskelas dan membandingkan nilai untuk masing-masing property. Jika salah satu nilainya berbeda, itu akan terjadi return false, jika tidak maka akan berbeda 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;
}

Pemakaian:

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


0

Untuk memperluas jawaban @nawfal: s, saya menggunakan ini untuk menguji objek dari berbagai jenis dalam pengujian unit saya untuk membandingkan nama properti yang sama. Dalam kasus saya, entitas database dan DTO.

Digunakan seperti ini dalam pengujian saya;

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

terkadang Anda tidak ingin membandingkan semua properti publik dan hanya ingin membandingkan subsetnya saja, jadi dalam hal ini Anda cukup memindahkan logika untuk membandingkan daftar properti yang diinginkan ke kelas abstrak

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

dan gunakan kelas abstrak ini nanti untuk membandingkan objek

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

    protected decimal DistanceInMeters { get; private set; }

    ...

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

0

solusi saya terinspirasi dari jawaban Aras Alenin di atas dimana saya menambahkan satu tingkat perbandingan objek dan objek khusus untuk hasil perbandingan. Saya juga tertarik untuk mendapatkan nama properti dengan nama objek:

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

Menggunakan kelas berikut untuk menyimpan hasil perbandingan

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

Dan uji unit sampel:

    [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");
    }
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.