2011-01-07 6 views
2

표현식 트리를 사용하여 형식의 인스턴스의 모든 속성을 해당 형식의 다른 인스턴스의 속성과 비교하는 메서드를 동적으로 생성하는 간단한 생성기를 작성하려고합니다. . 이것은 intstring과 같은 대부분의 속성에서는 정상적으로 작동하지만 DateTime? (및 다른 null 가능 값 유형)으로는 실패합니다. 표현 트리를 사용하여 개체의 모든 속성 비교

은있어서

static Delegate GenerateComparer(Type type) 
{ 
    var left = Expression.Parameter(type, "left"); 
    var right = Expression.Parameter(type, "right"); 

    Expression result = null; 

    foreach (var p in type.GetProperties()) 
    { 
    var leftProperty = Expression.Property(left, p.Name); 
    var rightProperty = Expression.Property(right, p.Name); 

    var equals = p.PropertyType.GetMethod("Equals", new[] { p.PropertyType }); 

    var callEqualsOnLeft = Expression.Call(leftProperty, equals, rightProperty); 

    result = result != null ? (Expression)Expression.And(result, callEqualsOnLeft) : (Expression)callEqualsOnLeft; 
    } 

    var method = Expression.Lambda(result, left, right).Compile(); 

    return method; 

} 

이 함께 실패 DateTime? 재산 : 유형

식 'System.Nullable`1 [System.DateTime] 유형의 매개 변수를 사용할 수 없습니다 'System.Object'메서드의 '부울 같음 (System.Object)'

확인, 따라서을 예상하는 Equals의 오버로드를 찾습니다.. 그렇다면 왜 DateTime?을 전달할 수 있습니까? object으로 변환 가능합니까? Nullable<T>을 보면 실제로는 Equals(object o)의 재정의가 있습니다.

PS : 나는 그것을 null 값을 처리 할 수 ​​없기 때문에이 적절한 발전기는 아직 아니라는 것을 알지만 그 :)

UPDATE를 얻을 것이다 :이라 클리스 '대답했다 이 특별한 문제를 해결하기 위해 노력했지만, 결국 나는 충분하다고 생각하는 훨씬 더 간단한 접근법을 찾으러 갔다 : 단지 Expression.Equal을 사용하라. 내 케이스의 99 %를 커버한다고 생각합니다 (==을 무시하지 않고 Equals을 무시할 수 있는지 확실하지 않지만 괜찮습니다).

답변

2

당신이 유형이 코드에 널 (NULL) 확인하는 경우 작동 될 수 있습니다

if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)){} 

코드 샘플 here
에서입니다 그리고 그들은이 널 경우 다음

Nullable.Equals<T>(T? n1, T? n2); 
+0

감사합니다. 그 방법으로 내 문제가 해결되었지만, 결국에는 다른 접근 방식으로 모두갔습니다. – stringargs

0

를 호출 할 수 있습니다 내가 사용할 수있는 무언가를 웹에서 검색 한 후, 직접 구현하기로 결정했다. 표현 트리를 사용하지 않았습니다. 대신 리플렉션을 사용하여 모든 속성을 검사하고 ToString()을 사용하여 비교합니다. 속성이 컬렉션 인 경우 컬렉션의 모든 요소가 동일한 지 비교합니다.

다음은 코드입니다.

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Reflection; 
using System.Linq; 

namespace Utils 
{ 
    public class PropertyComparer<T> : IEqualityComparer<T> 
    { 
     public bool Equals(T x, T y) 
     { 
      IEnumerable<PropertyInfo> allProperties = typeof(T).GetProperties(); 
      foreach(PropertyInfo pi in allProperties) 
      { 
       if (pi.GetCustomAttributes<EqualityIrrelevantAttribute>().Any()) 
       { 
        continue; 
       } 

       object xProp = pi.GetValue(x); 
       object yProp = pi.GetValue(y); 

       if ((xProp == null) && (yProp == null)) 
       { 
        continue; 
       } 
       else if ((xProp == null) || (yProp == null)) 
       { 
        return false; 
       } 
       else if (xProp is ICollection) 
       { 
        if (!CollectionsEqual(xProp as ICollection, yProp as ICollection)) 
        { 
         return false; 
        } 
       } 

       if (xProp.ToString() != yProp.ToString()) 
       { 
        return false; 
       } 
      } 

      return true; 
     } 

     bool CollectionsEqual(ICollection left, ICollection right) 
     { 
      IEnumerator leftEnumerator = left.GetEnumerator(); 
      IEnumerator rightEnumerator = right.GetEnumerator(); 

      bool leftAdvanced = leftEnumerator.MoveNext(); 
      bool rightAdvanced = rightEnumerator.MoveNext(); 

      if ((leftAdvanced && !rightAdvanced) || (rightAdvanced && !leftAdvanced)) 
      { 
       return false; 
      } 
      else if (!leftAdvanced && !rightAdvanced) 
      { 
       return true; 
      } 

      bool compareByClass = false; 
      object comparer = null; 
      MethodInfo equalsMethod = null; 

      // Inspect type first 
      object peek = leftEnumerator.Current; 
      Type valuesType = peek.GetType(); 
      if (valuesType.IsClass) 
      { 
       compareByClass = true; 
       Type comparerType = typeof(PropertyComparer<>).MakeGenericType(new Type[] { valuesType }); 
       equalsMethod = comparerType.GetMethod("Equals", new Type[] { valuesType, valuesType }); 
       comparer = Activator.CreateInstance(comparerType); 
      } 


      leftEnumerator.Reset(); 
      rightEnumerator.Reset(); 

      while (true) 
      { 
       leftAdvanced = leftEnumerator.MoveNext(); 
       rightAdvanced = rightEnumerator.MoveNext(); 

       if ((leftAdvanced && !rightAdvanced) || (rightAdvanced && !leftAdvanced)) 
       { 
        return false; 
       } 
       else if (!leftAdvanced && !rightAdvanced) 
       { 
        return true; 
       } 

       object leftValue = leftEnumerator.Current; 
       object rightValue = rightEnumerator.Current; 

       if (compareByClass) 
       { 
        bool result = (bool)equalsMethod.Invoke(comparer, new object[] { leftValue, rightValue }); 
        if (!result) 
        { 
         return false; 
        } 
       } 
       else if (leftEnumerator.Current.ToString() != rightEnumerator.Current.ToString()) 
       { 
        return false; 
       } 

       // Continue looping 
      } 
     } 

     public int GetHashCode(T obj) 
     { 
      throw new NotImplementedException(); 
     } 
    } 
} 

클래스의 속성 자체가 클래스 인 경우 해당 클래스의 속성을 비교할 수있는 새로운 비교자를 만듭니다. 또한 선택적으로 EqualityIrrelevantAttribute을 사용하여 비교 대상에서 제외 할 특정 속성을 표시 할 수도 있습니다. 그것은 저를 위해 진짜로 잘 작동합니다, 다른 사람들이 유용하다고 생각하기를 바랍니다.