내가 좋아하는이 개 복잡한 객체를 가지고 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;
}
}
답변
두 객체를 직렬화 한 다음 해시 코드를 계산 한 다음 비교합니다.