Saya memiliki dua objek kompleks seperti Object1dan Object2. Mereka memiliki sekitar 5 tingkat objek anak.
Saya membutuhkan metode tercepat untuk mengatakan apakah mereka sama atau tidak.
Bagaimana ini bisa dilakukan di C # 4.0?
Saya memiliki dua objek kompleks seperti Object1dan Object2. Mereka memiliki sekitar 5 tingkat objek anak.
Saya membutuhkan metode tercepat untuk mengatakan apakah mereka sama atau tidak.
Bagaimana ini bisa dilakukan di C # 4.0?
Jawaban:
Terapkan IEquatable<T>(biasanya bersamaan dengan mengganti metode Object.Equalsdan yang diwariskan Object.GetHashCode) di semua jenis kustom Anda. Dalam kasus tipe komposit, panggil metode tipe yang terkandung Equalsdi dalam tipe yang memuat. Untuk koleksi yang terkandung, gunakan SequenceEqualmetode ekstensi, yang memanggil secara internal IEquatable<T>.Equalsatau Object.Equalspada setiap elemen. Pendekatan ini jelas akan meminta Anda untuk memperluas definisi tipe Anda, tetapi hasilnya lebih cepat daripada solusi umum yang melibatkan serialisasi.
Sunting : Berikut adalah contoh yang dibuat-buat dengan tiga tingkat bersarang.
Untuk tipe nilai, Anda biasanya dapat memanggil Equalsmetodenya. Meskipun bidang atau properti tidak pernah ditetapkan secara eksplisit, mereka akan tetap memiliki nilai default.
Untuk jenis referensi, Anda harus memanggil terlebih dahulu ReferenceEquals, yang memeriksa persamaan referensi - ini akan berfungsi sebagai peningkatan efisiensi saat Anda kebetulan merujuk objek yang sama. Ini juga akan menangani kasus di mana kedua referensi bernilai null. Jika pemeriksaan itu gagal, konfirmasikan bahwa bidang atau properti instance Anda bukan null (untuk dihindari NullReferenceException) dan panggil Equalsmetodenya. Karena anggota kami diketik dengan benar, IEquatable<T>.Equalsmetode ini dipanggil secara langsung, melewati Object.Equalsmetode yang ditimpa (yang eksekusinya akan sedikit lebih lambat karena tipe cast).
Saat Anda mengganti Object.Equals, Anda juga diharapkan untuk mengganti Object.GetHashCode; Saya tidak melakukannya di bawah ini demi keringkasan.
public class Person : IEquatable<Person>
{
public int Age { get; set; }
public string FirstName { get; set; }
public Address Address { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as Person);
}
public bool Equals(Person other)
{
if (other == null)
return false;
return this.Age.Equals(other.Age) &&
(
object.ReferenceEquals(this.FirstName, other.FirstName) ||
this.FirstName != null &&
this.FirstName.Equals(other.FirstName)
) &&
(
object.ReferenceEquals(this.Address, other.Address) ||
this.Address != null &&
this.Address.Equals(other.Address)
);
}
}
public class Address : IEquatable<Address>
{
public int HouseNo { get; set; }
public string Street { get; set; }
public City City { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as Address);
}
public bool Equals(Address other)
{
if (other == null)
return false;
return this.HouseNo.Equals(other.HouseNo) &&
(
object.ReferenceEquals(this.Street, other.Street) ||
this.Street != null &&
this.Street.Equals(other.Street)
) &&
(
object.ReferenceEquals(this.City, other.City) ||
this.City != null &&
this.City.Equals(other.City)
);
}
}
public class City : IEquatable<City>
{
public string Name { get; set; }
public override bool Equals(object obj)
{
return this.Equals(obj as City);
}
public bool Equals(City other)
{
if (other == null)
return false;
return
object.ReferenceEquals(this.Name, other.Name) ||
this.Name != null &&
this.Name.Equals(other.Name);
}
}
Pembaruan : Jawaban ini ditulis beberapa tahun yang lalu. Sejak itu, saya mulai menjauh dari penerapan IEquality<T>tipe yang bisa berubah untuk skenario semacam itu. Ada dua pengertian tentang persamaan: identitas dan kesetaraan . Pada tingkat representasi memori, ini secara populer dibedakan sebagai "persamaan referensi" dan "persamaan nilai" (lihat Perbandingan Kesetaraan ). Namun, perbedaan yang sama juga dapat diterapkan di tingkat domain. Misalkan Personkelas Anda memiliki PersonIdproperti, unik per orang di dunia nyata yang berbeda. Haruskah dua objek dengan nilai yang sama PersonIdtetapi berbeda Agedianggap sama atau berbeda? Jawaban di atas mengasumsikan bahwa seseorang berada setelah persamaan. Namun, ada banyak penggunaan fileIEquality<T>antarmuka, seperti koleksi, yang mengasumsikan bahwa implementasi tersebut menyediakan identitas . Misalnya, jika Anda mengisi a HashSet<T>, Anda biasanya mengharapkan TryGetValue(T,T)panggilan untuk mengembalikan elemen yang ada yang hanya berbagi identitas argumen Anda, tidak harus elemen setara yang isinya benar-benar sama. Gagasan ini diperkuat oleh catatan di GetHashCode:
Secara umum, untuk jenis referensi yang bisa berubah, Anda harus mengganti
GetHashCode()hanya jika:
- Anda dapat menghitung kode hash dari bidang yang tidak bisa berubah; atau
- Anda dapat memastikan bahwa kode hash dari objek yang bisa berubah tidak berubah saat objek tersebut berada dalam koleksi yang bergantung pada kode hashnya.
partial- dalam hal ini, ya, Anda dapat menerapkan Equalsmetode mereka melalui deklarasi kelas parsial yang ditambahkan secara manual yang mereferensikan bidang / properti dari yang dibuat secara otomatis satu.
Enumerable.SequenceEqualmetode pada array: this.Addresses.SequenceEqual(other.Addresses). Ini secara internal akan memanggil Address.Equalsmetode Anda untuk setiap pasangan alamat yang sesuai, mengingat bahwa Addresskelas mengimplementasikan IEquatable<Address>antarmuka.
Serialisasi kedua objek dan bandingkan string yang dihasilkan
+1ini hanya karena saya tidak pernah berpikir untuk melakukan perbandingan kesetaraan berbasis nilai dengan cara ini. Itu bagus dan sederhana. Akan rapi untuk melihat beberapa tolok ukur dengan kode ini.
Anda dapat menggunakan metode ekstensi, rekursi untuk menyelesaikan masalah ini:
public static bool DeepCompare(this object obj, object another)
{
if (ReferenceEquals(obj, another)) return true;
if ((obj == null) || (another == null)) return false;
//Compare two object's class, return false if they are difference
if (obj.GetType() != another.GetType()) return false;
var result = true;
//Get all properties of obj
//And compare each other
foreach (var property in obj.GetType().GetProperties())
{
var objValue = property.GetValue(obj);
var anotherValue = property.GetValue(another);
if (!objValue.Equals(anotherValue)) result = false;
}
return result;
}
public static bool CompareEx(this object obj, object another)
{
if (ReferenceEquals(obj, another)) return true;
if ((obj == null) || (another == null)) return false;
if (obj.GetType() != another.GetType()) return false;
//properties: int, double, DateTime, etc, not class
if (!obj.GetType().IsClass) return obj.Equals(another);
var result = true;
foreach (var property in obj.GetType().GetProperties())
{
var objValue = property.GetValue(obj);
var anotherValue = property.GetValue(another);
//Recursion
if (!objValue.DeepCompare(anotherValue)) result = false;
}
return result;
}
atau bandingkan dengan menggunakan Json (jika objek sangat kompleks) Anda dapat menggunakan Newtonsoft.Json:
public static bool JsonCompare(this object obj, object another)
{
if (ReferenceEquals(obj, another)) return true;
if ((obj == null) || (another == null)) return false;
if (obj.GetType() != another.GetType()) return false;
var objJson = JsonConvert.SerializeObject(obj);
var anotherJson = JsonConvert.SerializeObject(another);
return objJson == anotherJson;
}
DeepComparedaripada hanya menelepon CompareExsecara rekursif?
resultdengan return falseakan membuatnya lebih efisien.
Jika Anda tidak ingin mengimplementasikan IEquatable, Anda selalu dapat menggunakan Reflection untuk membandingkan semua properti: - jika mereka adalah tipe nilai, bandingkan saja -jika mereka adalah tipe referensi, panggil fungsi secara rekursif untuk membandingkan properti "dalam" -nya .
Saya tidak berpikir tentang performa, tapi tentang kesederhanaan. Itu tergantung, bagaimanapun pada desain yang tepat dari objek Anda. Ini bisa menjadi rumit tergantung pada bentuk objek Anda (misalnya jika ada ketergantungan siklik antar properti). Namun, ada beberapa solusi di luar sana yang dapat Anda gunakan, seperti ini:
Pilihan lainnya adalah menserialkan objek sebagai teks, misalnya menggunakan JSON.NET, dan membandingkan hasil serialisasi. (JSON.NET dapat menangani dependensi Cyclic antar properti).
Saya tidak tahu apakah yang Anda maksud tercepat adalah cara tercepat untuk menerapkannya atau kode yang berjalan cepat. Anda tidak harus mengoptimalkan sebelum mengetahui jika Anda perlu. Optimalisasi dini adalah akar dari segala kejahatan
IEquatable<T>penerapan memenuhi syarat sebagai kasus pengoptimalan prematur. Refleksi akan menjadi lebih lambat secara drastis. Implementasi default Equalsuntuk tipe nilai kustom tidak menggunakan refleksi; Microsoft sendiri menyarankan untuk menimpanya untuk performa: "Ganti Equalsmetode untuk jenis tertentu guna meningkatkan performa metode dan lebih dekat mewakili konsep kesetaraan untuk jenis tersebut".
Serialisasi kedua objek dan bandingkan string yang dihasilkan dengan @JoelFan
Jadi untuk melakukan ini, buat kelas statis seperti itu dan gunakan Ekstensi untuk memperluas SEMUA objek (sehingga Anda dapat meneruskan semua jenis objek, koleksi, dll ke dalam metode)
using System;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;
public static class MySerializer
{
public static string Serialize(this object obj)
{
var serializer = new DataContractJsonSerializer(obj.GetType());
using (var ms = new MemoryStream())
{
serializer.WriteObject(ms, obj);
return Encoding.Default.GetString(ms.ToArray());
}
}
}
Setelah Anda mereferensikan kelas statis ini di file lain, Anda dapat melakukan ini:
Person p = new Person { Firstname = "Jason", LastName = "Argonauts" };
Person p2 = new Person { Firstname = "Jason", LastName = "Argonaut" };
//assuming you have already created a class person!
string personString = p.Serialize();
string person2String = p2.Serialize();
Sekarang Anda cukup menggunakan .Equals untuk membandingkannya. Saya menggunakan ini untuk memeriksa apakah objek ada dalam koleksi juga. Ini bekerja dengan sangat baik.
CultrureInfo. Ini hanya akan berfungsi jika sebagian besar data internal berupa string dan bilangan bulat. Kalau tidak, itu akan menjadi bencana.
Saya akan berasumsi bahwa Anda tidak mengacu pada objek yang sama secara harfiah
Object1 == Object2
Anda mungkin berpikir untuk melakukan perbandingan memori antara keduanya
memcmp(Object1, Object2, sizeof(Object.GetType())
Tapi itu bahkan bukan kode sebenarnya di c # :). Karena semua data Anda mungkin dibuat di heap, memori tidak bersebelahan dan Anda tidak bisa begitu saja membandingkan kesetaraan dua objek secara agnostik. Anda harus membandingkan setiap nilai, satu per satu, dengan cara khusus.
Pertimbangkan untuk menambahkan IEquatable<T>antarmuka ke kelas Anda, dan tentukan Equalsmetode kustom untuk tipe Anda. Kemudian, dalam metode itu, uji manual setiap nilai. Tambahkan IEquatable<T>lagi pada tipe tertutup jika Anda bisa dan ulangi prosesnya.
class Foo : IEquatable<Foo>
{
public bool Equals(Foo other)
{
/* check all the values */
return false;
}
}
Serialisasi kedua objek, lalu hitung Kode Hash, lalu bandingkan.
Saya menemukan fungsi di bawah ini untuk membandingkan objek.
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 (object.Equals(Object1, default(T)) || object.Equals(object2, default(T)))
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;
}
Saya menggunakannya dan berfungsi dengan baik untuk saya.
ifberarti Compare(null, null) == falsebukan itu yang saya harapkan.
Berdasarkan beberapa jawaban yang sudah diberikan di sini, saya memutuskan untuk mendukung sebagian besar jawaban JoelFan . Saya suka metode ekstensi dan ini telah bekerja dengan baik untuk saya ketika tidak ada solusi lain yang akan menggunakannya untuk membandingkan kelas kompleks saya.
using System.IO;
using System.Xml.Serialization;
static class ObjectHelpers
{
public static string SerializeObject<T>(this T toSerialize)
{
XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());
using (StringWriter textWriter = new StringWriter())
{
xmlSerializer.Serialize(textWriter, toSerialize);
return textWriter.ToString();
}
}
public static bool EqualTo(this object obj, object toCompare)
{
if (obj.SerializeObject() == toCompare.SerializeObject())
return true;
else
return false;
}
public static bool IsBlank<T>(this T obj) where T: new()
{
T blank = new T();
T newObj = ((T)obj);
if (newObj.SerializeObject() == blank.SerializeObject())
return true;
else
return false;
}
}
if (record.IsBlank())
throw new Exception("Record found is blank.");
if (record.EqualTo(new record()))
throw new Exception("Record found is blank.");
Saya akan mengatakan bahwa:
Object1.Equals(Object2)
akan menjadi apa yang Anda cari. Itu jika Anda ingin melihat apakah objeknya sama, yang sepertinya Anda tanyakan.
Jika Anda ingin memeriksa apakah semua objek turunannya sama, jalankan melalui loop dengan Equals()metode.
public class GetObjectsComparison
{
public object FirstObject, SecondObject;
public BindingFlags BindingFlagsConditions= BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
}
public struct SetObjectsComparison
{
public FieldInfo SecondObjectFieldInfo;
public dynamic FirstObjectFieldInfoValue, SecondObjectFieldInfoValue;
public bool ErrorFound;
public GetObjectsComparison GetObjectsComparison;
}
private static bool ObjectsComparison(GetObjectsComparison GetObjectsComparison)
{
GetObjectsComparison FunctionGet = GetObjectsComparison;
SetObjectsComparison FunctionSet = new SetObjectsComparison();
if (FunctionSet.ErrorFound==false)
foreach (FieldInfo FirstObjectFieldInfo in FunctionGet.FirstObject.GetType().GetFields(FunctionGet.BindingFlagsConditions))
{
FunctionSet.SecondObjectFieldInfo =
FunctionGet.SecondObject.GetType().GetField(FirstObjectFieldInfo.Name, FunctionGet.BindingFlagsConditions);
FunctionSet.FirstObjectFieldInfoValue = FirstObjectFieldInfo.GetValue(FunctionGet.FirstObject);
FunctionSet.SecondObjectFieldInfoValue = FunctionSet.SecondObjectFieldInfo.GetValue(FunctionGet.SecondObject);
if (FirstObjectFieldInfo.FieldType.IsNested)
{
FunctionSet.GetObjectsComparison =
new GetObjectsComparison()
{
FirstObject = FunctionSet.FirstObjectFieldInfoValue
,
SecondObject = FunctionSet.SecondObjectFieldInfoValue
};
if (!ObjectsComparison(FunctionSet.GetObjectsComparison))
{
FunctionSet.ErrorFound = true;
break;
}
}
else if (FunctionSet.FirstObjectFieldInfoValue != FunctionSet.SecondObjectFieldInfoValue)
{
FunctionSet.ErrorFound = true;
break;
}
}
return !FunctionSet.ErrorFound;
}
Salah satu cara untuk melakukannya adalah dengan mengganti Equals()setiap jenis yang terlibat. Misalnya, objek tingkat atas Anda akan menimpa Equals()untuk memanggil Equals()metode dari semua 5 objek turunan. Semua objek tersebut juga harus diganti Equals(), dengan asumsi mereka adalah objek kustom, dan seterusnya hingga seluruh hierarki dapat dibandingkan hanya dengan melakukan pemeriksaan kesetaraan pada objek tingkat atas.
Gunakan IEquatable<T>Antarmuka yang memiliki metode Equals.
Berkat teladan Jonathan. Saya mengembangkannya untuk semua kasus (array, daftar, kamus, tipe primitif).
Ini adalah perbandingan tanpa serialisasi dan tidak memerlukan implementasi antarmuka apa pun untuk objek yang dibandingkan.
/// <summary>Returns description of difference or empty value if equal</summary>
public static string Compare(object obj1, object obj2, string path = "")
{
string path1 = string.IsNullOrEmpty(path) ? "" : path + ": ";
if (obj1 == null && obj2 != null)
return path1 + "null != not null";
else if (obj2 == null && obj1 != null)
return path1 + "not null != null";
else if (obj1 == null && obj2 == null)
return null;
if (!obj1.GetType().Equals(obj2.GetType()))
return "different types: " + obj1.GetType() + " and " + obj2.GetType();
Type type = obj1.GetType();
if (path == "")
path = type.Name;
if (type.IsPrimitive || typeof(string).Equals(type))
{
if (!obj1.Equals(obj2))
return path1 + "'" + obj1 + "' != '" + obj2 + "'";
return null;
}
if (type.IsArray)
{
Array first = obj1 as Array;
Array second = obj2 as Array;
if (first.Length != second.Length)
return path1 + "array size differs (" + first.Length + " vs " + second.Length + ")";
var en = first.GetEnumerator();
int i = 0;
while (en.MoveNext())
{
string res = Compare(en.Current, second.GetValue(i), path);
if (res != null)
return res + " (Index " + i + ")";
i++;
}
}
else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(type))
{
System.Collections.IEnumerable first = obj1 as System.Collections.IEnumerable;
System.Collections.IEnumerable second = obj2 as System.Collections.IEnumerable;
var en = first.GetEnumerator();
var en2 = second.GetEnumerator();
int i = 0;
while (en.MoveNext())
{
if (!en2.MoveNext())
return path + ": enumerable size differs";
string res = Compare(en.Current, en2.Current, path);
if (res != null)
return res + " (Index " + i + ")";
i++;
}
}
else
{
foreach (PropertyInfo pi in type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
{
try
{
var val = pi.GetValue(obj1);
var tval = pi.GetValue(obj2);
if (path.EndsWith("." + pi.Name))
return null;
var pathNew = (path.Length == 0 ? "" : path + ".") + pi.Name;
string res = Compare(val, tval, pathNew);
if (res != null)
return res;
}
catch (TargetParameterCountException)
{
//index property
}
}
foreach (FieldInfo fi in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
{
var val = fi.GetValue(obj1);
var tval = fi.GetValue(obj2);
if (path.EndsWith("." + fi.Name))
return null;
var pathNew = (path.Length == 0 ? "" : path + ".") + fi.Name;
string res = Compare(val, tval, pathNew);
if (res != null)
return res;
}
}
return null;
}
Untuk memudahkan penyalinan kode yang dibuat repositori
Sekarang Anda dapat menggunakan json.net. Buka saja Nuget dan pasang.
Dan Anda bisa melakukan sesuatu seperti ini:
public bool Equals(SamplesItem sampleToCompare)
{
string myself = JsonConvert.SerializeObject(this);
string other = JsonConvert.SerializeObject(sampleToCompare);
return myself == other;
}
Anda mungkin bisa membuat metode ekstensi untuk objek jika Anda ingin menjadi lebih menarik. Harap dicatat ini hanya membandingkan properti publik. Dan jika Anda ingin mengabaikan properti publik saat melakukan perbandingan, Anda dapat menggunakan atribut [JsonIgnore].