Ben gibi iki karmaşık nesneleri Object1ve Object2. Yaklaşık 5 seviyeli çocuk nesneleri var.
Aynı olup olmadıklarını söylemek için en hızlı yönteme ihtiyacım var.
Bu C # 4.0'da nasıl yapılabilir?
Ben gibi iki karmaşık nesneleri Object1ve Object2. Yaklaşık 5 seviyeli çocuk nesneleri var.
Aynı olup olmadıklarını söylemek için en hızlı yönteme ihtiyacım var.
Bu C # 4.0'da nasıl yapılabilir?
Yanıtlar:
Tüm özel türlerinize uygulayın IEquatable<T>(tipik olarak devralınan Object.Equalsve Object.GetHashCodeyöntemleri geçersiz kılmakla birlikte ). Bileşik türler durumunda, içerilen türlerin Equalsyöntemini içeren türler içinde çağırın . İçerilen koleksiyonlar için, SequenceEqualdahili olarak çağıran uzantı yöntemini kullanın IEquatable<T>.EqualsveyaObject.Equals her bir öğeyi . Bu yaklaşım tabii ki türlerinizin tanımlarını genişletmenizi gerektirecektir, ancak sonuçları serileştirmeyi içeren tüm genel çözümlerden daha hızlıdır.
Düzenleme : İşte üç seviyeli iç içe geçme ile uydurma bir örnek.
Değer türleri için, genellikle yalnızca Equalsyöntemlerini çağırabilirsiniz . Alanlar veya özellikler hiçbir zaman açıkça atanmamış olsa bile, yine de varsayılan bir değere sahip olacaklardır.
Referans türleri için, önce ReferenceEqualsreferans eşitliğini kontrol eden bir çağrı yapmalısınız - bu, aynı nesneye referans verdiğinizde bir verimlilik artışı olarak hizmet edecektir. Ayrıca, her iki başvurunun da boş olduğu durumları da ele alır. Bu kontrol başarısız olursa, örneğinizin alanının veya özelliğinin boş olmadığını (önlemek için NullReferenceException) onaylayın ve Equalsyöntemini çağırın . Üyelerimiz düzgün şekilde yazıldığından, IEquatable<T>.Equalsyöntem geçersiz kılınan Object.Equalsyöntemi atlayarak doğrudan çağrılır ( bu yöntemin yürütülmesi, türden dolayı marjinal olarak daha yavaş olacaktır).
Geçersiz kıldığınızda Object.Equals, geçersiz kılmanız da beklenir Object.GetHashCode; Bunu aşağıda özlülük uğruna yapmadım.
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);
}
}
Güncelleme : Bu cevap birkaç yıl önce yazılmıştır. O zamandan beri, bu IEquality<T>tür senaryolar için değişken türler uygulamaktan uzaklaşmaya başladım . İki eşitlik kavramı vardır: özdeşlik ve denklik . Bellek temsili düzeyinde, bunlar popüler olarak "referans eşitliği" ve "değer eşitliği" olarak ayırt edilir (bkz. Eşitlik Karşılaştırmaları ). Bununla birlikte, aynı ayrım bir alan düzeyinde de geçerli olabilir. Sizin . Örneğin, a dolduruyorsanız, genellikle bir çağrının yalnızca argümanınızın kimliğini paylaşan var olan öğeleri döndürmesini beklersiniz , içeriği tamamen aynı olan eşdeğer öğeler değil. Bu kavram,PersonSınıfınızın her bir PersonIdgerçek dünya insanı için benzersiz bir özelliği . Aynı PersonIdancak farklı Agedeğerlere sahip iki nesne eşit mi yoksa farklı mı kabul edilmelidir? Yukarıdaki cevap, birinin denkliğin peşinde olduğunu varsayar. Ancak, birçok kullanım şekli vardır.IEquality<T>Bu tür uygulamaların sağladığını varsayan koleksiyonlar gibi arayüz kimlikHashSet<T>TryGetValue(T,T)GetHashCode:
Genel olarak, değiştirilebilir referans türleri için,
GetHashCode()yalnızca aşağıdaki durumlarda geçersiz kılmalısınız :
- Karma kodu değiştirilebilir olmayan alanlardan hesaplayabilirsiniz; veya
- Değişken bir nesnenin karma kodunun, nesne karma koduna dayanan bir koleksiyonda yer alırken değişmemesini sağlayabilirsiniz.
partial- bu durumda, evet, Equalsyöntemlerini, otomatik olarak oluşturulan alanlara / özelliklere başvuran manuel olarak eklenen kısmi bir sınıf bildirimi aracılığıyla uygulayabilirsiniz. bir.
Enumerable.SequenceEqualdiziler üzerinde yöntemini: this.Addresses.SequenceEqual(other.Addresses). Bu Address.Equals, Addresssınıfın IEquatable<Address>arabirimi uyguladığı göz önüne alındığında, her bir karşılık gelen adres çifti için yönteminizi dahili olarak çağırır .
Her iki nesneyi de seri hale getirin ve ortaya çıkan dizeleri karşılaştırın
+1basitçe yapıyorum çünkü bu şekilde değer temelli bir eşitlik karşılaştırması yapmayı hiç düşünmemiştim. Güzel ve basit. Bu kodla bazı karşılaştırmalar görmek güzel olurdu.
Bu sorunu çözmek için uzatma yöntemini, özyinelemeyi kullanabilirsiniz:
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;
}
veya Json kullanarak karşılaştırın (nesne çok karmaşıksa) Newtonsoft.Json'ı kullanabilirsiniz:
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;
}
DeepCompareBasitçe CompareExözyinelemeli aramak yerine kullanmak için herhangi bir sebep var mı ?
resultile return falsedaha verimli hale getirecektir.
IEquatable'ı uygulamak istemiyorsanız, tüm özellikleri karşılaştırmak için her zaman Reflection'ı kullanabilirsiniz: - değer tipiyse, sadece karşılaştırın - referans tipiyse, "iç" özelliklerini karşılaştırmak için işlevi özyinelemeli olarak çağırın .
Performans hakkında değil, basitlik hakkında düşünüyorum. Bununla birlikte, nesnelerinizin tam tasarımına bağlıdır. Nesnenizin şekline bağlı olarak karmaşıklaşabilir (örneğin, özellikler arasında döngüsel bağımlılıklar varsa). Bununla birlikte, bunun gibi kullanabileceğiniz birkaç çözüm var:
Başka bir seçenek de nesneyi metin olarak serileştirmek, örneğin JSON.NET kullanarak ve serileştirme sonucunu karşılaştırmaktır. (JSON.NET, özellikler arasındaki Döngüsel bağımlılıkları işleyebilir).
En hızlı derken onu uygulamanın en hızlı yolunu mu yoksa hızlı çalışan bir kodu mu kastettiğinizi bilmiyorum. İhtiyacınız olup olmadığını bilmeden optimizasyon yapmamalısınız. Erken optimizasyon, tüm kötülüklerin köküdür
IEquatable<T>uygulamanın erken optimizasyon durumu olarak nitelendirildiğini pek düşünmüyorum . Yansıma büyük ölçüde yavaşlayacak. EqualsÖzel değer türleri için varsayılan uygulaması yansıma kullanır; Microsoft, performans için onu geçersiz kılmayı öneriyor: " EqualsYöntemin performansını iyileştirmek ve tür için eşitlik kavramını daha yakından temsil etmek için belirli bir tür için yöntemi geçersiz kılın ."
Her iki nesneyi de serileştirin ve elde edilen dizeleri @JoelFan ile karşılaştırın
Bunu yapmak için, buna benzer bir statik sınıf oluşturun ve TÜM nesneleri genişletmek için Uzantıları kullanın (böylece her türden nesne, koleksiyon, vb. Yönteme geçirebilirsiniz)
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());
}
}
}
Bu statik sınıfa başka bir dosyada başvurduğunuzda, şunu yapabilirsiniz:
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();
Şimdi bunları karşılaştırmak için .Equals kullanabilirsiniz. Bunu nesnelerin koleksiyonda olup olmadığını kontrol etmek için de kullanıyorum. Gerçekten iyi çalışıyor.
CultrureInfo. Bu, yalnızca dahili veriler çoğunlukla dizeler ve tam sayılardan oluşuyorsa işe yarar. Aksi takdirde felaket olur.
Kelimenin tam anlamıyla aynı nesnelerden bahsetmediğini varsayacağım
Object1 == Object2
İkisi arasında bir bellek karşılaştırması yapmayı düşünüyor olabilirsiniz
memcmp(Object1, Object2, sizeof(Object.GetType())
Ama bu c # :) için gerçek kod bile değil. Tüm verileriniz muhtemelen yığın üzerinde oluşturulduğundan, bellek bitişik değildir ve iki nesnenin eşitliğini agnostik bir şekilde karşılaştıramazsınız. Her değeri teker teker özel bir şekilde karşılaştırmanız gerekecek.
IEquatable<T>Arayüzü sınıfınıza eklemeyi düşünün ve Equalstürünüz için özel bir yöntem tanımlayın . Ardından, bu yöntemde her bir değeri manuel olarak test edin. IEquatable<T>Yapabiliyorsanız ekli türlerde tekrar ekleyin ve işlemi tekrarlayın.
class Foo : IEquatable<Foo>
{
public bool Equals(Foo other)
{
/* check all the values */
return false;
}
}
Her iki nesneyi de seri hale getirin, ardından Karma Kodunu hesaplayın, ardından karşılaştırın.
Nesneleri karşılaştırmak için bu işlevi aşağıda buldum.
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;
}
Kullanıyorum ve benim için iyi çalışıyor.
ifanlamına geliyor Compare(null, null) == false.
Burada zaten verilen birkaç cevaba dayanarak JoelFan'in cevabını çoğunlukla desteklemeye karar verdim . Uzatma yöntemlerini seviyorum ve bunlar, diğer çözümlerin hiçbiri karmaşık sınıflarımı karşılaştırmak için kullanmadığında benim için harika çalışıyor.
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.");
Şunu söylemek isterim:
Object1.Equals(Object2)
aradığınız şey olurdu. Bu, nesnelerin aynı olup olmadığını görmek istiyorsan, sorduğun şey bu.
Tüm alt nesnelerin aynı olup olmadığını kontrol etmek istiyorsanız, bunları Equals()yöntemle bir döngüden geçirin .
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;
}
Bunu yapmanın bir yolu, Equals()dahil olan her türü geçersiz kılmak olacaktır . Örneğin, üst düzey nesneniz, 5 alt nesnenin tümünün yöntemini Equals()çağırmak için geçersiz kılar Equals(). Bu nesnelerin tümü Equals()de, özel nesneler oldukları varsayılarak geçersiz kılınmalıdır ve tüm hiyerarşi, üst düzey nesnelerde yalnızca bir eşitlik kontrolü gerçekleştirilerek karşılaştırılabilene kadar bu şekilde devam etmelidir .
IEquatable<T>Yöntemi olan Arayüzü kullanın Equals.
Jonathan örneği sayesinde. Tüm durumlar için genişlettim (diziler, listeler, sözlükler, ilkel türler).
Bu, serileştirme olmadan bir karşılaştırmadır ve karşılaştırılan nesneler için herhangi bir arabirimin uygulanmasını gerektirmez.
/// <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;
}
Oluşturulan kod deposunun kolay kopyalanması için
Artık json.net'i kullanabilirsiniz. Sadece Nuget'e gidin ve kurun.
Ve bunun gibi bir şey yapabilirsiniz:
public bool Equals(SamplesItem sampleToCompare)
{
string myself = JsonConvert.SerializeObject(this);
string other = JsonConvert.SerializeObject(sampleToCompare);
return myself == other;
}
Daha meraklı olmak istiyorsanız, belki nesne için bir genişletme yöntemi yapabilirsiniz. Lütfen bunun yalnızca kamusal mülkleri karşılaştırdığını unutmayın. Karşılaştırmayı yaptığınızda bir kamu malı görmezden gelmek isterseniz, [JsonIgnore] özelliğini kullanabilirsiniz.