2016-09-29 2 views
0

표현 트리 및 연산자 오버로딩과 관련하여 이상한 문제가 있습니다 (특히 ==!= 연산자 사용). 내가 마크 Gravell의 답변 중 하나에서 MemberwiseComparer을 사용하고표현식 트리의 같음이 올바른 연산자 오버로드를 사용하지 않습니다.

더 많거나 적은 한

public static class MemberComparer 
{ 
    public static bool Equal<T>(T x, T y) 
    { 
     return EqualComparerCache<T>.Compare(x, y); 
    } 

    static class EqualComparerCache<T> 
    { 
     internal static readonly Func<T, T, bool> Compare = (a, b) => true; 

     static EqualComparerCache() 
     { 
      var members = typeof(T).GetTypeInfo().DeclaredProperties.Cast<MemberInfo>() 
       .Concat(typeof(T).GetTypeInfo().DeclaredFields.Where(p => !p.IsStatic && p.IsPublic).Cast<MemberInfo>()); 
      var x = Expression.Parameter(typeof(T), "x"); 
      var y = Expression.Parameter(typeof(T), "y"); 

      Expression body = null; 
      foreach (var member in members) 
      { 
       Expression memberEqual; 
       if (member is FieldInfo) 
       { 
        memberEqual = Expression.Equal(
         Expression.Field(x, (FieldInfo)member), 
         Expression.Field(y, (FieldInfo)member)); 
       } 
       else if (member is PropertyInfo) 
       { 
        memberEqual = Expression.Equal(
         Expression.Property(x, (PropertyInfo)member), 
         Expression.Property(y, (PropertyInfo)member)); 
       } 
       else 
       { 
        throw new NotSupportedException(member.GetType().GetTypeInfo().Name); 
       } 

       body = body == null ? memberEqual : Expression.AndAlso(body, memberEqual); 
      } 

      if (body != null) 
      { 
       var lambda = Expression.Lambda<Func<T, T, bool>>(body, x, y); 
       Compare = lambda.Compile(); 
      } 
     } 
    } 
} 

그리고 값 객체에 대한 기본 클래스 역할을하는 기본 클래스 ValueObject<T>.

public class ValueObject<T> : IEquatable<T> where T : ValueObject<T> 
{ 
    public virtual bool Equals(T other) 
    { 
     if (ReferenceEquals(this, other)) 
      return true; 

     return MemberComparer.Equal<T>((T)this, other); 
    } 

    public override bool Equals(object obj) 
    { 
     return Equals(obj as T); 
    } 

    public override int GetHashCode() 
    { 
     return MemberComparer.GetHashCode((T)this); 
    } 

    public static bool operator ==(ValueObject<T> left, ValueObject<T> right) 
    { 
     // If both are null, or both are same instance, return true. 
     if (ReferenceEquals(left, right)) 
     { 
      return true; 
     } 

     // If one is null, but not both, return false. 
     if (((object)left == null) || ((object)right == null)) 
     { 
      return false; 
     } 

     return left.Equals(right); 
    } 

    public static bool operator !=(ValueObject<T> left, ValueObject<T> right) 
    { 
     return !(left == right); 
    } 
} 

는 일반적으로이 IEquatable<T> 또는 스칼라 유형 및/또는 문자열을 구현하는 클래스를 위해 잘 작동합니다. 그러나 클래스에 ValueObject<T>을 구현하는 클래스 인 속성이 포함되어 있으면 비교가 실패합니다. TestTest를 비교할 때

public class Test : ValueObject<Test> 
{ 
    public string Value { get; set; } 
} 

public class Test2 : ValueObject<Test2> 
{ 
    public Test Test { get; set; } 
} 

는 그것을 잘 작동합니다. Test2을 비교할 때

var test1 = new Test { Value = "TestValue"; } 
var test2 = new Test { Value = "TestValue"; } 

Assert.True(test1==test2); // true 
Assert.Equals(test1, test2); // true 

은 그러나 실패

var nestedTest1 = new Test2 { Test = new Test { Value = "TestValue"; } } 
var nestedTest2 = new Test2 { Test = new Test { Value = "TestValue"; } } 

Assert.True(nestedTest1==nestedTest2); // false 
Assert.Equals(nestedTest1, nestedTest2); // false 

// Second Test with referenced Test object 
var test = new Test { Value = "TestValue"; } 
var nestedTest1 = new Test2 { Test = test } 
var nestedTest2 = new Test2 { Test = test } 

Assert.True(nestedTest1==nestedTest2); // true 
Assert.Equals(nestedTest1, nestedTest2); // true 

== 운영자 재정이 Test2 클래스라고하지만 Test 클래스된다. nestedTest1nestedTest2이 동일한 Test 개체를 참조하면 작동합니다. 따라서 표현식이 컴파일되고 컴파일 될 때 == 오버로드가 호출되지 않습니다.

나는 그것을 무시할 수없는 이유를 찾지 못했습니다. 아무도 눈치 채지 못했거나 표현 트리 생성에 문제가 있습니까?

물론 Expression Tree 생성을 .Equals 메서드를 호출하도록 다시 작성할 수는 있지만 더 복잡한 (및 추가 null 검사)가 추가됩니다. 하지만 실제 질문은 왜 컴파일 된 Expression Tree가 == 과부하를 사용하지 않고 어떻게 작동 시키는가하는 것입니다.

답변

0

op_Equality 연산자 대체 메서드를 검색하고이를 네 번째 매개 변수로 Expression.Equal에 전달하는 방법을 구현합니다.그것은 첫째 op_Equality 발견 사용 (지금은) 충분한입니다 내 간단한 시나리오에서

MethodInfo equalsOperator = FindMethod(memberType, "op_Equality", false); 

equalityExpression = Expression.Equal(
    Expression.Property(left, memberInfo), 
    Expression.Property(right, memberInfo), 
    false, 
    equalsOperator); 

... 
private static MethodInfo FindMethod(Type type, string methodName, bool throwIfNotFound = true) 
{ 
    TypeInfo typeInfo = type.GetTypeInfo(); 

    // TODO: Improve to search methods with a specific signature and parameters 
    while (typeInfo != null) 
    { 
     IEnumerable<MethodInfo> methodInfo = typeInfo.GetDeclaredMethods(methodName); 
     if (methodInfo.Any()) 
      return methodInfo.First(); 

     typeInfo = typeInfo.BaseType?.GetTypeInfo(); 
    } 

    if (!throwIfNotFound) 
     return null; 

    throw new InvalidOperationException($"Type '{type.GetTypeInfo().FullName}' has no '{methodName}' method."); 
} 

ValueObject<T> 클래스에 하나 이하가되어야하며, 두 객체가의 때 나는 MemberComparer.Equal<T>((T)this, other) 만이라고 확인했다 같은 유형.

2

조금 파고 들자, 여기에 문제가 있습니다. 연산자 ==은 클래스 Test에 정의되어 있지 않지만 ValueType<T>에 정의되어 있습니다. 당신이 호출하면

,

// this is used by Expression.Equal (it does not search for base type) 

var m = typeof(Test).GetMethod("op_Equality", 
      BindingFlags.Static 
      | BindingFlags.Public | BindingFlags.NonPublic); 

//m is null because op_Equality is not declared on "Test" 

var m = typeof(ValueObject<>).GetMethod("op_Equality", 
      BindingFlags.Static 
      | BindingFlags.Public | BindingFlags.NonPublic); 

// m is not null 

이 연산자 평등 방법을 사용하지 않는 이유 식입니다.

Roslyn은 컴파일 할 때 등호 연산자를 사용하지만 식 컴파일러는 Roslyn의 일부가 아니며 기본 클래스의 메서드를 검색하지 않는 행 http://referencesource.microsoft.com/#System.Core/Microsoft/Scripting/Ast/BinaryExpression.cs,b3df2869d7601af4에 버그가있는 것으로 보입니다.

+0

처음에는 생각했지만 Assert.True (test1 == test2);가 작동했으나 같은 식 (memberEqual = Expression.Equal (Expression.Property (x , (PropertyInfo) member), Expression.Property (y, (PropertyInfo) member));')는 표현 트리를 통해 생성되지 않습니다. 위의 경우, breakpoint를 설정하면 == 연산자가 호출됩니다. – Tseng

+0

문자열에 대한 연산자가 문자열 클래스에 정의되어 있기 때문입니다. 또한 C# 컴파일러와 표현식 컴파일러에는 차이가 있습니다. == 연산자는 C# 컴파일러에 의해 호출됩니다. 버그일지도 모릅니다. –

관련 문제