とのような2つの複雑なオブジェクトがObject1
ありObject2
ます。それらには約5レベルの子オブジェクトがあります。
それらが同じかどうかを言うための最速の方法が必要です。
これはC#4.0でどのように行うことができますか?
すべてのカスタムタイプに(通常、継承されたメソッドとメソッドIEquatable<T>
のオーバーライドと組み合わせて)実装します。複合型の場合、包含型内で包含型のメソッドを呼び出します。含まれているコレクションの場合は、内部でまたは各要素を呼び出す拡張メソッドを使用します。このアプローチでは、明らかに型の定義を拡張する必要がありますが、その結果は、シリアル化を含む一般的なソリューションよりも高速です。Object.Equals
Object.GetHashCode
Equals
SequenceEqual
IEquatable<T>.Equals
Object.Equals
編集:これは、3つのレベルのネストを使用した不自然な例です。
値型の場合、通常はそのEquals
メソッドを呼び出すだけです。フィールドまたはプロパティが明示的に割り当てられていない場合でも、デフォルト値があります。
参照型の場合、最初にを呼び出す必要がReferenceEquals
あります。これは、参照の同等性をチェックします。これは、同じオブジェクトを参照している場合の効率の向上に役立ちます。また、両方の参照がnullの場合も処理します。そのチェックが失敗した場合は、インスタンスのフィールドまたはプロパティがnullでないことを確認し(回避するため)、そのメソッドNullReferenceException
を呼び出します。Equals
メンバーは適切に型指定されているため、IEquatable<T>.Equals
メソッドは直接呼び出され、オーバーライドされたObject.Equals
メソッドをバイパスします(型キャストのために実行がわずかに遅くなります)。
をオーバーライドすると、 ;Object.Equals
もオーバーライドすることが期待されます。Object.GetHashCode
簡潔にするために、以下ではそうしませんでした。
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);
}
}
更新:この回答は数年前に書かれました。それ以来、私はIEquality<T>
そのようなシナリオの可変型の実装から離れ始めました。平等には、同一性と同等性という2つの概念があります。メモリ表現レベルでは、これらは一般に「参照の同等性」と「値の同等性」として区別されます(同等性の比較を参照)。ただし、同じ区別がドメインレベルでも当てはまります。Person
クラスに、PersonId
実世界の人物ごとに一意のプロパティがあるとします。PersonId
同じであるが異なる値を持つ2つのオブジェクトは、Age
等しいまたは異なると見なされるべきですか?上記の答えは、1つが同等性の後であることを前提としています。ただし、の多くの使用法がありますIEquality<T>
コレクションなどのインターフェースで、そのような実装がIDを提供することを前提としています。たとえば、を入力する場合、通常は、引数のIDのみを共有する既存の要素を返す呼び出しをHashSet<T>
期待しますが、内容が完全に同じである同等の要素である必要はありません。TryGetValue(T,T)
この概念は、次の注記によって実施されますGetHashCode
。
GetHashCode()
一般に、可変参照型の場合、次の場合にのみオーバーライドする必要があります。
- 変更できないフィールドからハッシュコードを計算できます。また
- オブジェクトがそのハッシュコードに依存するコレクションに含まれている間、可変オブジェクトのハッシュコードが変更されないようにすることができます。
両方のオブジェクトをシリアル化し、結果の文字列を比較します
この問題を解決するには、拡張メソッド、再帰を使用できます。
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;
}
または、Jsonを使用して比較します(オブジェクトが非常に複雑な場合)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;
}
IEquatableを実装したくない場合は、いつでもReflectionを使用してすべてのプロパティを比較できます。--値型の場合は比較するだけです。-参照型の場合は、関数を再帰的に呼び出して「内部」プロパティを比較します。 。
私はパフォーマンスについてではなく、シンプルさについて考えています。ただし、オブジェクトの正確なデザインによって異なります。オブジェクトの形状によっては複雑になる可能性があります(たとえば、プロパティ間に循環依存がある場合)。ただし、次のように、使用できるソリューションがいくつかあります。
もう1つのオプションは、たとえばJSON.NETを使用してオブジェクトをテキストとしてシリアル化し、シリアル化の結果を比較することです。(JSON.NETはプロパティ間の循環依存関係を処理できます)。
最速でそれを実装する最速の方法なのか、それとも高速に実行されるコードなのかはわかりません。必要かどうかを知る前に最適化しないでください。時期尚早の最適化はすべての悪の根源です
両方のオブジェクトをシリアル化し、結果の文字列を@JoelFanで比較します
したがって、これを行うには、そのような静的クラスを作成し、Extensionsを使用してすべてのオブジェクトを拡張します(これにより、任意のタイプのオブジェクト、コレクションなどをメソッドに渡すことができます)
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());
}
}
}
この静的クラスを他のファイルで参照すると、次のことができます。
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();
これで、.Equalsを使用してそれらを比較できます。これは、オブジェクトがコレクションにあるかどうかを確認するためにも使用します。それは本当にうまくいきます。
両方のオブジェクトをシリアル化し、ハッシュコードを計算してから、比較します。
これで、json.netを使用できます。Nugetに移動してインストールするだけです。
そして、あなたはこのようなことをすることができます:
public bool Equals(SamplesItem sampleToCompare)
{
string myself = JsonConvert.SerializeObject(this);
string other = JsonConvert.SerializeObject(sampleToCompare);
return myself == other;
}
もっと凝ったものにしたいのなら、おそらくオブジェクトの拡張メソッドを作ることができます。これは公共のプロパティのみを比較することに注意してください。また、比較を行うときにパブリックプロパティを無視したい場合は、[JsonIgnore]
属性を使用できます。
文字通り同じオブジェクトを参照していないと思います
Object1 == Object2
あなたは2つの間のメモリ比較を行うことを考えているかもしれません
memcmp(Object1, Object2, sizeof(Object.GetType())
しかし、それはc#の実際のコードではありません:)。すべてのデータはおそらくヒープ上に作成されるため、メモリは連続しておらず、2つのオブジェクトの同等性を不可知論的な方法で比較することはできません。カスタムの方法で、一度に1つずつ各値を比較する必要があります。
クラスにインターフェースを追加することを検討し、タイプIEquatable<T>
のカスタムEquals
メソッドを定義します。次に、その方法で、各値を手動でテストします。可能であれば、同封のタイプに再度追加IEquatable<T>
して、プロセスを繰り返します。
class Foo : IEquatable<Foo>
{
public bool Equals(Foo other)
{
/* check all the values */
return false;
}
}
不変のクラスが必要な場合。つまり、一度作成したプロパティは変更できません。その場合、C#9にはレコードと呼ばれる機能があります。
レコードが等しい場合は、値とタイプでレコードを簡単に比較できます。
public record Person
{
public string LastName { get; }
public string FirstName { get; }
public Person(string first, string last) => (FirstName, LastName) = (first, last);
}
var person1 = new Person("Bill", "Wagner");
var person2 = new Person("Bill", "Wagner");
Console.WriteLine(person1 == person2); // true
私はそれを言うでしょう:
Object1.Equals(Object2)
あなたが探しているものになります。それは、オブジェクトが同じであるかどうかを確認しようとしている場合です。これは、あなたが求めているように見えるものです。
すべての子オブジェクトが同じであるかどうかを確認する場合は、Equals()
メソッドを使用してループを実行します。
オブジェクトを比較するための以下の関数を見つけました。
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;
}
私はそれを使用しています、そしてそれは私のためにうまく働いています。
ここですでに与えられたいくつかの答えに基づいて、私は主にJoelFanの答えを支持することにしました。私は拡張メソッドが大好きで、他のどのソリューションも私の複雑なクラスを比較するためにそれらを使用しないときに、これらは私にとってうまく機能しています。
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.");
ジョナサンの例に感謝します。すべての場合(配列、リスト、辞書、プリミティブ型)に拡張しました。
これはシリアル化なしの比較であり、比較対象のオブジェクトにインターフェイスを実装する必要はありません。
/// <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;
}
コードで作成されたリポジトリを簡単にコピーするため
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;
}
これを行う1つの方法は、Equals()
関係する各タイプをオーバーライドすることです。たとえば、最上位のオブジェクトは、5つの子オブジェクトすべてEquals()
のメソッドを呼び出すようにオーバーライドします。Equals()
これらのオブジェクトは、カスタムオブジェクトであると仮定して、すべてオーバーライドする必要がありEquals()
ます。最上位のオブジェクトに対して同等性チェックを実行するだけで階層全体を比較できるようになるまで、以下同様です。
IEquatable<T>
メソッドを持つインターフェースを使用しEquals
ます。
ジェネリック拡張メソッド
public static class GenericExtensions
{
public static bool DeepCompare<T>(this T objA, T objB)
{
if (typeof(T).IsValueType)
return objA.Equals(objB);
if (ReferenceEquals(objA, objB))
return true;
if ((objA == null) || (objB == null))
return false;
if (typeof(T) is IEnumerable)
{
var enumerableA = (IEnumerable<T>) objA;
var enumerableB = (IEnumerable<T>) objB;
if (enumerableA.Count() != enumerableB.Count())
return false;
using (var enumeratorA = enumerableA.GetEnumerator())
using (var enumeratorB = enumerableB.GetEnumerator())
{
while (true)
{
bool moveNextA = enumeratorA.MoveNext();
bool moveNextB = enumeratorB.MoveNext();
if (!moveNextA || !moveNextB)
break;
var currentA = enumeratorA.Current;
var currentB = enumeratorB.Current;
if (!currentA.DeepCompare<T>(currentB))
return false;
}
return true;
}
}
foreach (var property in objA.GetType().GetProperties())
{
var valueA = property.GetValue(objA);
var valueB = property.GetValue(objB);
if (!valueA.DeepCompare(valueB))
return false;
}
return true;
}
}