2012-04-11 6 views
11

테스트 프레임 워크를 만드는 동안 이상한 문제가 발견되었습니다.Type.GetProperties() 및 람다 식에서 PropertyInfo를 비교하십시오.

동일한 유형의 객체를 해당 속성으로 비교할 수 있지만 일부는 무시할 수있는 정적 클래스를 만들고 싶습니다.

나는 이것에 대한 간단한 유창하게 API를 갖고 싶어, 그래서 주어진 객체가 IdName을 제외한 모든 특성 (가 어떤지를 검사하지 않습니다)에 동일한 경우 TestEqualityComparer.Equals(first.Ignore(x=>x.Id).Ignore(y=>y.Name), second); 같은 호출은 true를 돌려줍니다.

여기 내 코드가 있습니다. 물론 그것은 간단한 예제입니다 (일부 명백한 방법의 누락 된 과부하),하지만 나는 가능한 가장 간단한 코드를 추출하고 싶었습니다. 실제 사례 시나리오는 좀 더 복잡하므로이 접근 방식을 변경하고 싶지 않습니다.

FindProperty 방법은 AutoMapper library에서 거의 복사 붙여 넣기입니다. 유창 API에 대한

개체 래퍼 :

public class TestEqualityHelper<T> 
{ 
    public List<PropertyInfo> IgnoredProps = new List<PropertyInfo>(); 
    public T Value; 
} 

유창함 물건 :

public static class FluentExtension 
{ 
    //Extension method to speak fluently. It finds the property mentioned 
    // in 'ignore' parameter and adds it to the list. 
    public static TestEqualityHelper<T> Ignore<T>(this T value, 
     Expression<Func<T, object>> ignore) 
    { 
     var eh = new TestEqualityHelper<T> { Value = value }; 

     //Mind the magic here! 
     var member = FindProperty(ignore); 
     eh.IgnoredProps.Add((PropertyInfo)member); 
     return eh; 
    } 

    //Extract the MemberInfo from the given lambda 
    private static MemberInfo FindProperty(LambdaExpression lambdaExpression) 
    { 
     Expression expressionToCheck = lambdaExpression; 

     var done = false; 

     while (!done) 
     { 
      switch (expressionToCheck.NodeType) 
      { 
       case ExpressionType.Convert: 
        expressionToCheck 
         = ((UnaryExpression)expressionToCheck).Operand; 
        break; 
       case ExpressionType.Lambda: 
        expressionToCheck 
         = ((LambdaExpression)expressionToCheck).Body; 
        break; 
       case ExpressionType.MemberAccess: 
        var memberExpression 
         = (MemberExpression)expressionToCheck; 

        if (memberExpression.Expression.NodeType 
          != ExpressionType.Parameter && 
         memberExpression.Expression.NodeType 
          != ExpressionType.Convert) 
        { 
         throw new Exception("Something went wrong"); 
        } 

        return memberExpression.Member; 
       default: 
        done = true; 
        break; 
      } 
     } 

     throw new Exception("Something went wrong"); 
    } 
} 

실제 비교 자 : 기본적으로의

public static class TestEqualityComparer 
{ 
    public static bool MyEquals<T>(TestEqualityHelper<T> a, T b) 
    { 
     return DoMyEquals(a.Value, b, a.IgnoredProps); 
    } 

    private static bool DoMyEquals<T>(T a, T b, 
     IEnumerable<PropertyInfo> ignoredProperties) 
    { 
     var t = typeof(T); 
     IEnumerable<PropertyInfo> props; 

     if (ignoredProperties != null && ignoredProperties.Any()) 
     { 
      //THE PROBLEM IS HERE! 
      props = 
       t.GetProperties(BindingFlags.Instance | BindingFlags.Public) 
        .Except(ignoredProperties); 
     } 
     else 
     { 
      props = 
       t.GetProperties(BindingFlags.Instance | BindingFlags.Public); 
     } 
     return props.All(f => f.GetValue(a, null).Equals(f.GetValue(b, null))); 
    } 
} 

.

그리고 여기에 두 개의 테스트 조각이며, 첫 번째 작품, 두 번째는 실패

//These are the simple objects we'll compare 
public class Base 
{ 
    public decimal Id { get; set; } 
    public string Name { get; set; } 
} 
public class Derived : Base 
{ } 

[TestMethod] 
public void ListUsers() 
{ 
    //TRUE 
    var f = new Base { Id = 5, Name = "asdas" }; 
    var s = new Base { Id = 6, Name = "asdas" }; 
    Assert.IsTrue(TestEqualityComparer.MyEquals(f.Ignore(x => x.Id), s)); 

    //FALSE 
    var f2 = new Derived { Id = 5, Name = "asdas" }; 
    var s2 = new Derived { Id = 6, Name = "asdas" }; 
    Assert.IsTrue(TestEqualityComparer.MyEquals(f2.Ignore(x => x.Id), s2)); 
} 

문제는 DoMyEquals에서 Except 방법이다.

FindProperty에 의해 반환되는 속성은 Type.GetProperties에 의해 반환되는 것과 동일하지 않습니다. 차이점은 PropertyInfo.ReflectedType입니다.

  • 에 관계없이 내 개체의 유형, FindProperty은 반사 형이 Base 것을 알려줍니다. Type.GetProperties에 의해 반환

  • 특성은 실제 개체의 유형에 따라 Base 또는 Derived 자신의 ReflectedType 세트가있다.

나는 그것을 해결하는 방법을 모른다. 람다에서 매개 변수의 유형을 확인할 수는 있지만 다음 단계에서는 Ignore(x=>x.Some.Deep.Property)과 같은 구조를 허용하므로 아마도 그렇게하지 않을 것입니다.

PropertyInfo을 비교하는 방법이나 lambda에서 적절히 검색하는 방법에 대한 제안은 감사하겠습니다.

+1

GetProperties에서 BindingFlags.FlattenHierarchy 값으로 재생 해 보았습니까? 그것이 뭐가 바뀌는 지 보시오. –

+1

거기에는 행운이 없지만 제안 해 주셔서 감사합니다. 나는 ** BindingFlags가 반환되는 멤버 만 변경할 수 있지만 자신의 속성에는 영향을 미치지 않을 것이라고 생각합니다. 나는이 솔루션이 FindProperty와 관련되어있을 것이라고 믿는다. –

+1

구성원 이름으로 GetProperty (표현식을 통해 얻을 수도 있음) 유형을 얻은 후에 FindProperty에 두 번째 해킹 된 단계를 추가 할 수 있습니까? 해킹이지만 작동 할 수도 있습니다. –

답변

5

이유는 FindPropertyTypeBase이라는 것을 나타냅니다. 이는 람다가 호출에 사용할 클래스이기 때문입니다.

당신은 아마 당신이 람다 직접 자료 방법을 사용하여 실제로 이유에 대한 자세한 내용을 설명하기 위해이

static IEnumerable<PropertyInfo> GetMappedProperties(Type type) 
{ 
    return type 
    .GetProperties() 
    .Select(p => GetMappedProperty(type, p.Name)) 
    .Where(p => p != null); 
} 

static PropertyInfo GetMappedProperty(Type type, string name) 
{ 
    if (type == null) 
    return null; 

    var prop = type.GetProperty(name); 

    if (prop.DeclaringType == type) 
    return prop; 
    else 
    return GetMappedProperty(type.BaseType, name); 
} 

를 사용할 수 있습니다,이 대신 유형에서 GetProperties를()의

을 :) 알고, 당신

static void Foo() 
{ 
    var b = new Base { Id = 4 }; 
    var d = new Derived { Id = 5 }; 

    decimal dm = b.Id; 
    dm = d.Id; 
} 
: 다른 PropertyInfo은 고려 IL

보고 설명한 더 좋을 수도, 본질적으로이 코드를 참조

그리고 여기가 d.Id

에 대한 b.Id

IL_002f: callvirt instance valuetype [mscorlib]System.Decimal ConsoleApplication1.Base::get_Id() 

그리고 IL의 IL이다

IL_0036: callvirt instance valuetype [mscorlib]System.Decimal ConsoleApplication1.Base::get_Id() 
+0

괜찮아 보이지만, 난 정말로 모르겠다. 왜 람다가베이스를 사용합니까? 캐스팅이 없습니다.'lambdaExpression.Parameters [0] .Type'은'Derived'라고 말합니다. 왜 이런 일이 일어 났는지 설명해 주시겠습니까? (설명 링크 또는 일부 키워드가 충분하지 않을 수도 있습니다 ;-)) –

+0

람다 매개 변수 유형은 선언 된대로 매개 변수의 유형이지만 실제 메소드 호출은 기본 유형을 사용하여 호출합니다 (메소드는 기본 유형). – payo

+0

@xavier 내 답변에 추가 정보를 참조하십시오 [또한,이 솔루션이 작동하고 6 명이 그것을 upvoted 경우 - 심지어 하나의 별표, 왜 내 대답에 대한 사랑 :(그래서 때로는 이해가 안 돼요) – payo

5
이 도움이된다면

이 몰라,하지만 난 나타났습니다 그 MetaDataToken 속성 두 인스턴스가 둘 중 하나의 ReflectedType에 관계없이 동일한 논리적 특성을 참조하는 경우 두 PropertyInfo 인스턴스의 값이 같습니다. 즉, PropertyInfo 인스턴스의 Name, PropertyType, DeclaringType 및 index 매개 변수는 모두 동일합니다.

+3

이것은 매우 흥미 롭습니다! msdn에 따르면,'MetadataToken'은'Module'과 결합하여 요소를 고유하게 식별합니다. 고맙습니다! –

관련 문제