2009-05-17 3 views
2

참조 루프를 포함하지 않는 두 객체가 있다고 가정 할 때, (반사를 통해) "일반적인"방식으로 동등성을 테스트하는 방법을 알고 있습니까?심층 구조체와 같은 Equals() for .NET 클래스?

기본적으로 클래스에 대해서만 구조체 동등성과 동일한 의미를 원합니다.

답변

3

프레임 워크에서 사용할 수있는 방법이 없다고 생각하지만 상당히 쉽게 작성되었습니다. 각 호출에 반영하고없이 을 수행 할 경우

private bool AreEqual(object x, object y) 
{ 
    // if both are null, they are equal 
    if (x == null && y == null) 
    { 
     return true; 
    } 
    // if one of them are null, they are not equal 
    else if (x == null || y == null) 
    { 
     return false; 
    } 

    // if they are of different types, they can't be compared 
    if (x.GetType() != y.GetType()) 
    { 
     throw new InvalidOperationException("x and y must be of the same type"); 
    } 

    Type type = x.GetType(); 
    PropertyInfo[] properties = type.GetProperties(); 

    for (int i = 0; i < properties.Length; i++) 
    { 
     // compare only properties that requires no parameters 
     if (properties[i].GetGetMethod().GetParameters().Length == 0) 
     { 
      object xValue = properties[i].GetValue(x, null); 
      object yValue = properties[i].GetValue(y, null); 

      if (properties[i].PropertyType.IsValueType && !xValue.Equals(yValue)) 
      { 
       return false; 
      } 
      else if (!properties[i].PropertyType.IsValueType) 
      { 
       if (!AreEqual(xValue, yValue)) 
       { 
        return false; 
       } 
      } // if 
     } // if 
    } // for 

    return true; 

} 
+0

이 내가 왔어요 몇 가지 문제를 가지고 이것을 사용할 때. 속성 그래프에주기가있는 경우 - 즉, 객체의 속성을 재귀 적으로 탐색하여 원래 객체로 끝낼 수있는 경우 - StackOverflowException이 발생합니다. 코드의 출처는 많이 알려짐). 두 번째로 getter가 예외를 throw하면 예외가 발생합니다. System.Type 유형이 주기적이며 던지는 속성을 가지고 있다는 것을 깨닫기 전까지는 너무 위험하지 않습니다. : – Avish

+0

순환 문제는 반복적으로 일련의 "방문"(x, y) 쌍을 돌아가며 사이클을 시작할 때 재귀를 중지함으로써 해결할 수 있지만 예외 문제는 지속적으로 해결할 수 없습니다 (우리는 x와 y에서 같은 예외가 발생 했습니까? 아니면이 속성을 무시합니까?) 어떤 경우에도이 문제는 보이는 것보다 더 복잡하며 매우 특정한 클래스에 포함하지 않는 한 이러한 종류의 비교를 수행하지 않는 것이 좋습니다. 당신은 그냥 Equals()를 구현할 수 있고 그것으로 끝낼 수 있습니다.) – Avish

+0

@Avish : 아주 좋은 관찰과 어느 정도 평소에 Equals를 구현하거나 커스텀 비교 방법을 선호하는 이유 –

0

하면, 먼저 호출에 DynamicMethod을 구축하고 그 대신에 사용하는 것을 고려하는 것이 좋습니다 : 아마도하지 짧은 구현하지만 일을 할 것으로 보인다 . (나는이 작업을 수행 기사에 대한 링크를했다,하지만 난 그것을 손실 - 죄송합니다 -. 관심이 경우 인터넷 검색을 시도)

+0

Phew! DynamicMethod는 Reflection Emit입니다. 즉, 각 속성에 대해 사용자 지정 MSIL을 만듭니다. 반드시 어렵지는 않지만 읽기 또는 디버그가 쉽지는 않습니다. 성능이 그 문제의 상당 부분은 업무용으로 특화된 Equals 또는 IEqualityComparer 을 작성하는 것이 더 나은 방법입니다. –

0

이 BTW

Expression.Lambda<Func<T,T,bool>> Compile() 

동적 방법 빌더로 사용할 수 있습니다.

여전히 Expresison

0

를 구축하는 동안 다음 계정 Nullable와 순환 참조에 소요 프레드릭 Mörk의 대답에서 업데이트 된 버전의 반사를 사용해야합니다 :

public static bool AreEqual<T>(T x, T y) => 
    AreEqual(x, y, new HashSet<object>(new IdentityEqualityComparer<object>())); 

private static bool AreEqual(object x, object y, ISet<object> visited) 
{ 
    // if both are null, they are equal 
    if (x == null && y == null) return true; 

    // if one of them are null, they are not equal 
    if (x == null || y == null) return false; 

    // if they are of different types, they can't be compared 
    if (x.GetType() != y.GetType()) 
    { 
     throw new InvalidOperationException("x and y must be of the same type"); 
    } 

    // check for recursive references 
    if (visited.Contains(x)) return true; 
    if (visited.Contains(y)) return true; 
    visited.Add(x); 
    visited.Add(y); 

    var type = x.GetType(); 
    var properties = type.GetProperties(); 

    foreach (var property in properties) 
    { 
     // compare only properties that requires no parameters 
     if (property.GetGetMethod().GetParameters().Length == 0) 
     { 
      object xValue = property.GetValue(x, null); 
      object yValue = property.GetValue(y, null); 

      if (property.PropertyType.IsValueType) 
      { 
       // check for Nullable 
       if (xValue == null && yValue == null) continue; 
       if (xValue == null || yValue == null) return false; 
       if (!xValue.Equals(yValue)) return false; 
      } 

      if (!property.PropertyType.IsValueType) 
      { 
       if (!AreEqual(xValue, yValue, visited)) return false; 
      } 
     } 
    } 

    return true; 
} 

private class IdentityEqualityComparer<T> : IEqualityComparer<T> where T : class 
{ 
    public int GetHashCode(T value) => RuntimeHelpers.GetHashCode(value); 
    public bool Equals(T left, T right) => left == right; 
}