.NET クラス用の深い構造体に似た Equals() ですか?
-
22-08-2019 - |
質問
内部に参照ループを含まない 2 つのオブジェクトがある場合、「一般的な」方法 (リフレクションを通じて) でそれらの等価性をテストする方法をご存知ですか?
基本的に、クラスに対してのみ、構造体の等価性と同じセマンティクスが必要です。
解決
私は、フレームワークで利用できるような方法が存在しないと思うが、それはかなり簡単に書かれています。おそらくない最短の実装が、が、仕事をするように思われます:
private bool AreEqual(object x, object y)
{
// if both are null, they are equal
if (x == null && y == null)
{
return true;
}
// if one of them are null, they are not equal
else if (x == null || y == null)
{
return false;
}
// if they are of different types, they can't be compared
if (x.GetType() != y.GetType())
{
throw new InvalidOperationException("x and y must be of the same type");
}
Type type = x.GetType();
PropertyInfo[] properties = type.GetProperties();
for (int i = 0; i < properties.Length; i++)
{
// compare only properties that requires no parameters
if (properties[i].GetGetMethod().GetParameters().Length == 0)
{
object xValue = properties[i].GetValue(x, null);
object yValue = properties[i].GetValue(y, null);
if (properties[i].PropertyType.IsValueType && !xValue.Equals(yValue))
{
return false;
}
else if (!properties[i].PropertyType.IsValueType)
{
if (!AreEqual(xValue, yValue))
{
return false;
}
} // if
} // if
} // for
return true;
}
他のヒント
これをやりたい場合は それなし 呼び出しごとにリフレクションを行う場合は、 DynamicMethod
最初の呼び出し時にそれを代わりに使用します。(これについての記事へのリンクがありましたが、紛失してしまいました。ごめんなさい。興味があればグーグルしてみてください。)
ところで
Expression.Lambda<Func<T,T,bool>> Compile()
動的方法ビルダーとして使用することができる。
まだExpresisonを構築しながらリフレクションを使用する必要があります。
ここでアカウントNullable
と再帰参照にかかるフレドリックモルクの答えから更新されたバージョンです
public static bool AreEqual<T>(T x, T y) =>
AreEqual(x, y, new HashSet<object>(new IdentityEqualityComparer<object>()));
private static bool AreEqual(object x, object y, ISet<object> visited)
{
// if both are null, they are equal
if (x == null && y == null) return true;
// if one of them are null, they are not equal
if (x == null || y == null) return false;
// if they are of different types, they can't be compared
if (x.GetType() != y.GetType())
{
throw new InvalidOperationException("x and y must be of the same type");
}
// check for recursive references
if (visited.Contains(x)) return true;
if (visited.Contains(y)) return true;
visited.Add(x);
visited.Add(y);
var type = x.GetType();
var properties = type.GetProperties();
foreach (var property in properties)
{
// compare only properties that requires no parameters
if (property.GetGetMethod().GetParameters().Length == 0)
{
object xValue = property.GetValue(x, null);
object yValue = property.GetValue(y, null);
if (property.PropertyType.IsValueType)
{
// check for Nullable
if (xValue == null && yValue == null) continue;
if (xValue == null || yValue == null) return false;
if (!xValue.Equals(yValue)) return false;
}
if (!property.PropertyType.IsValueType)
{
if (!AreEqual(xValue, yValue, visited)) return false;
}
}
}
return true;
}
private class IdentityEqualityComparer<T> : IEqualityComparer<T> where T : class
{
public int GetHashCode(T value) => RuntimeHelpers.GetHashCode(value);
public bool Equals(T left, T right) => left == right;
}
所属していません StackOverflow