표현 트리 및 연산자 오버로딩과 관련하여 이상한 문제가 있습니다 (특히 ==
및 !=
연산자 사용). 내가 마크 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>
을 구현하는 클래스 인 속성이 포함되어 있으면 비교가 실패합니다. Test
와 Test
를 비교할 때
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
클래스된다. nestedTest1
및 nestedTest2
이 동일한 Test
개체를 참조하면 작동합니다. 따라서 표현식이 컴파일되고 컴파일 될 때 ==
오버로드가 호출되지 않습니다.
나는 그것을 무시할 수없는 이유를 찾지 못했습니다. 아무도 눈치 채지 못했거나 표현 트리 생성에 문제가 있습니까?
물론 Expression Tree 생성을 .Equals
메서드를 호출하도록 다시 작성할 수는 있지만 더 복잡한 (및 추가 null 검사)가 추가됩니다. 하지만 실제 질문은 왜 컴파일 된 Expression Tree가 ==
과부하를 사용하지 않고 어떻게 작동 시키는가하는 것입니다.
처음에는 생각했지만 Assert.True (test1 == test2);가 작동했으나 같은 식 (memberEqual = Expression.Equal (Expression.Property (x , (PropertyInfo) member), Expression.Property (y, (PropertyInfo) member));')는 표현 트리를 통해 생성되지 않습니다. 위의 경우, breakpoint를 설정하면 == 연산자가 호출됩니다. – Tseng
문자열에 대한 연산자가 문자열 클래스에 정의되어 있기 때문입니다. 또한 C# 컴파일러와 표현식 컴파일러에는 차이가 있습니다. == 연산자는 C# 컴파일러에 의해 호출됩니다. 버그일지도 모릅니다. –