式ツリーと演算子のオーバーロード(特に==
と!=
演算子)で何か問題があります。式ツリーの等価性が正しい演算子オーバーロードを使用していません
私はマルクGravellの答えの一つ、値オブジェクトの基本クラスとして機能します多かれ少なかれ1
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>
からMemberwiseComparerを使用しています。
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>
を実装するクラスであるプロパティが含まれている場合、比較は失敗します。
public class Test : ValueObject<Test>
{
public string Value { get; set; }
}
public class Test2 : ValueObject<Test2>
{
public Test Test { get; set; }
}
それが正常に動作しますTest
とTest
を比較します。
var test1 = new Test { Value = "TestValue"; }
var test2 = new Test { Value = "TestValue"; }
Assert.True(test1==test2); // true
Assert.Equals(test1, test2); // true
しかし、それが失敗したTest2
を比較するとき:
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
==
演算子のオーバーライドはなくTest
クラスのため、Test2
クラスのために呼ばれています。 nestedTest1
とnestedTest2
が同じTest
オブジェクトを参照すると動作します。したがって、式が構築され、コンパイルされるときに==
オーバーロードは呼び出されません。
私はそれを無視する理由を見つけることができませんでした。これはRoslynに何らかの変更がありますか?気づいた人はいませんか、または表現木の生成に何か問題がありますか?
もちろん、.Equals
メソッドを呼び出すように式ツリーの生成を書き直すことはできますが、これにより複雑さ(および追加のヌルチェック)が追加されます。しかし実際の質問は、なぜコンパイルされた式ツリーで==
の過負荷が使用されていないのですか?
私はそれも考えましたが、なぜ 'Assert.True(test1 == test2); 'はうまくいったのですが、同じ式(' memberEqual = Expression.Equal(Expression.Property(x 、(PropertyInfo)メンバ)、Expression.Property(y、(PropertyInfo)メンバ));)上記の場合、ブレークポイントを設定すると、==演算子が呼び出されます。 – Tseng
Stringの演算子は文字列クラスで定義されているためです。また、C#コンパイラと式コンパイラには違いがあります。 ==演算子は、C#コンパイラによって呼び出されます。それはバグかもしれない。 –