두 개의 복잡한 개체를 비교하는 가장 좋은 방법
내가 좋아하는이 개 복잡한 객체를 가지고 Object1
와 Object2
. 약 5 단계의 하위 개체가 있습니다.
같은지 아닌지 말할 수있는 가장 빠른 방법이 필요합니다.
C # 4.0에서 어떻게이 작업을 수행 할 수 있습니까?
모든 사용자 지정 형식에 대해 구현 IEquatable<T>
(일반적으로 상속 된 메서드 Object.Equals
및 Object.GetHashCode
메서드 재정의와 함께 )합니다. 복합 유형의 경우 포함 유형 Equals
내에서 포함 된 유형의 메소드를 호출하십시오 . 포함 된 컬렉션의 경우 SequenceEqual
내부적으로 IEquatable<T>.Equals
또는 Object.Equals
각 요소를 호출 하는 확장 메서드를 사용합니다 . 이 접근 방식은 분명히 유형의 정의를 확장해야하지만 그 결과는 직렬화와 관련된 일반 솔루션보다 빠릅니다.
편집 : 다음은 세 가지 수준의 중첩이있는 인위적인 예입니다.
값 유형의 경우 일반적으로 해당 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>
그러한 시나리오에 대해 변경 가능한 유형을 구현 하는 것에서 벗어나기 시작했습니다 . 평등에는 두 가지 개념이 있습니다. 정체성 과 동등성 . 메모리 표현 수준에서 이들은 일반적으로 "참조 같음"과 "값 같음"으로 구분됩니다 (같음 비교 참조 ). 그러나 동일한 구분이 도메인 수준에서도 적용될 수 있습니다. Person
클래스 PersonId
에 고유 한 실제 사람마다 고유 한 속성 이 있다고 가정합니다 . PersonId
같지만 다른 Age
값을 가진 두 개체를 동일하거나 다른 것으로 간주 해야합니까 ? 위의 대답은 하나가 동등성 뒤에 있다고 가정합니다. 그러나 많은 용도가 있습니다.IEquality<T>
이러한 구현이 identity를 제공한다고 가정하는 컬렉션과 같은 인터페이스 . 예를 들어를 채우는 경우 HashSet<T>
일반적으로 TryGetValue(T,T)
호출에서 인수의 ID 만 공유하는 기존 요소를 반환 할 것으로 예상 할 수 있으며 , 콘텐츠가 완전히 동일한 요소가 아닐 수도 있습니다. 이 개념은 다음에 대한 메모에 의해 시행됩니다 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을 사용하여 모든 속성을 비교할 수 있습니다.-값 유형이면 비교하기 만하면됩니다.-참조 유형 인 경우 함수를 재귀 적으로 호출하여 "내부"속성을 비교합니다. .
나는 성능에 대해 생각하지 않고 단순성에 대해 생각합니다. 그러나 개체의 정확한 디자인에 따라 다릅니다. 객체 모양에 따라 복잡해질 수 있습니다 (예 : 속성간에 순환 종속성이있는 경우). 그러나 다음과 같이 사용할 수있는 몇 가지 솔루션이 있습니다.
또 다른 옵션은 객체를 텍스트로 직렬화하는 것입니다. 예를 들어 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를 사용하여 비교할 수 있습니다. 개체가 컬렉션에 있는지 확인하기 위해 이것을 사용합니다. 정말 잘 작동합니다.
나는 당신이 말 그대로 동일한 객체를 언급하지 않는다고 가정합니다
Object1 == Object2
둘 사이의 메모리 비교를 고려할 수 있습니다.
memcmp(Object1, Object2, sizeof(Object.GetType())
그러나 그것은 C #의 실제 코드가 아닙니다. :). 모든 데이터가 힙에 생성 될 가능성이 높기 때문에 메모리는 연속적이지 않으며 두 개체의 동일성을 불가지론적인 방식으로 비교할 수 없습니다. 사용자 지정 방식으로 한 번에 하나씩 각 값을 비교해야합니다.
IEquatable<T>
클래스에 인터페이스를 추가하고 Equals
유형에 대한 사용자 정의 메소드를 정의 하십시오. 그런 다음 해당 방법에서 각 값을 수동으로 테스트합니다. 가능한 IEquatable<T>
경우 동봉 된 유형에 다시 추가 하고 프로세스를 반복하십시오.
class Foo : IEquatable<Foo>
{
public bool Equals(Foo other)
{
/* check all the values */
return false;
}
}
나는 물체를 비교하기 위해 아래 기능을 발견했습니다.
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;
}
나는 그것을 사용하고 있고 그것은 나를 위해 잘 작동합니다.
두 객체를 직렬화 한 다음 해시 코드를 계산 한 다음 비교합니다.
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;
}
이미 여기에 제공된 몇 가지 답변을 바탕으로 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.");
나는 이렇게 말할 것이다:
Object1.Equals(Object2)
당신이 찾고있는 것입니다. 물체가 동일한 지 확인하려는 경우입니다. 이것이 당신이 묻는 것처럼 보입니다.
모든 자식 개체가 동일한 지 확인하려면 Equals()
메서드 를 사용하여 루프를 통해 실행하십시오 .
이를 수행하는 한 가지 방법은 Equals()
관련된 각 유형 을 재정의 하는 것 입니다. 예를 들어 최상위 객체는 5 개의 자식 객체 모두 Equals()
의 Equals()
메서드 를 호출하도록 재정의 합니다. 이러한 개체는 모두 Equals()
사용자 지정 개체라고 가정하고 재정의해야하며 , 최상위 개체에 대해 동등성 검사를 수행하여 전체 계층 구조를 비교할 수있을 때까지 계속해야합니다.
IEquatable<T>
메소드가있는 Interface를 사용합니다 Equals
.
Jonathan의 예 덕분입니다. 모든 경우 (배열, 목록, 사전, 기본 유형)에 대해 확장했습니다.
이것은 직렬화가없는 비교이며 비교 된 객체에 대한 인터페이스 구현이 필요하지 않습니다.
/// <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;
}
코드 생성 리포지토리 를 쉽게 복사하기 위해
참고 URL : https://stackoverflow.com/questions/10454519/best-way-to-compare-two-complex-objects
'program story' 카테고리의 다른 글
Python에서 하위 프로세스, 다중 처리 및 스레드 중에서 결정합니까? (0) | 2020.08.20 |
---|---|
Razor / MVC3를 사용하여 AssemblyVersion을 웹 페이지로 가져 오는 데 문제가 있습니다. (0) | 2020.08.20 |
'URL'에서 'createObjectURL'실행 실패 : (0) | 2020.08.20 |
ES6 화살표 함수와 함께 jQuery $ (this) 사용 (이 바인딩 어휘) (0) | 2020.08.20 |
jQuery에서 목록 요소 내용의 배열 가져 오기 (0) | 2020.08.20 |