참조 루프를 포함하지 않는 두 객체가 있다고 가정 할 때, (반사를 통해) "일반적인"방식으로 동등성을 테스트하는 방법을 알고 있습니까?심층 구조체와 같은 Equals() for .NET 클래스?
기본적으로 클래스에 대해서만 구조체 동등성과 동일한 의미를 원합니다.
참조 루프를 포함하지 않는 두 객체가 있다고 가정 할 때, (반사를 통해) "일반적인"방식으로 동등성을 테스트하는 방법을 알고 있습니까?심층 구조체와 같은 Equals() for .NET 클래스?
기본적으로 클래스에 대해서만 구조체 동등성과 동일한 의미를 원합니다.
프레임 워크에서 사용할 수있는 방법이 없다고 생각하지만 상당히 쉽게 작성되었습니다. 각 호출에 반영하고없이 을 수행 할 경우
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
을 구축하고 그 대신에 사용하는 것을 고려하는 것이 좋습니다 : 아마도하지 짧은 구현하지만 일을 할 것으로 보인다 . (나는이 작업을 수행 기사에 대한 링크를했다,하지만 난 그것을 손실 - 죄송합니다 -. 관심이 경우 인터넷 검색을 시도)
Phew! DynamicMethod는 Reflection Emit입니다. 즉, 각 속성에 대해 사용자 지정 MSIL을 만듭니다. 반드시 어렵지는 않지만 읽기 또는 디버그가 쉽지는 않습니다. 성능이 그 문제의 상당 부분은 업무용으로 특화된 Equals 또는 IEqualityComparer
이 BTW
Expression.Lambda<Func<T,T,bool>> Compile()
동적 방법 빌더로 사용할 수 있습니다.
여전히 Expresison
를 구축하는 동안 다음 계정 Nullable
와 순환 참조에 소요 프레드릭 Mörk의 대답에서 업데이트 된 버전의 반사를 사용해야합니다 :
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;
}
이 내가 왔어요 몇 가지 문제를 가지고 이것을 사용할 때. 속성 그래프에주기가있는 경우 - 즉, 객체의 속성을 재귀 적으로 탐색하여 원래 객체로 끝낼 수있는 경우 - StackOverflowException이 발생합니다. 코드의 출처는 많이 알려짐). 두 번째로 getter가 예외를 throw하면 예외가 발생합니다. System.Type 유형이 주기적이며 던지는 속성을 가지고 있다는 것을 깨닫기 전까지는 너무 위험하지 않습니다. : – Avish
순환 문제는 반복적으로 일련의 "방문"(x, y) 쌍을 돌아가며 사이클을 시작할 때 재귀를 중지함으로써 해결할 수 있지만 예외 문제는 지속적으로 해결할 수 없습니다 (우리는 x와 y에서 같은 예외가 발생 했습니까? 아니면이 속성을 무시합니까?) 어떤 경우에도이 문제는 보이는 것보다 더 복잡하며 매우 특정한 클래스에 포함하지 않는 한 이러한 종류의 비교를 수행하지 않는 것이 좋습니다. 당신은 그냥 Equals()를 구현할 수 있고 그것으로 끝낼 수 있습니다.) – Avish
@Avish : 아주 좋은 관찰과 어느 정도 평소에 Equals를 구현하거나 커스텀 비교 방법을 선호하는 이유 –