이것은 다른 많은 클래스에 상속 된 클래스에 대한 메서드로 제가 생각 해낸 것입니다. 아이디어는 동일한 유형의 객체 속성을 간단하게 비교할 수 있다는 것입니다.
자, 이것은 작동하지만 내 코드의 품질을 향상시키기 위해 조사를 위해 그것을 버릴 것이라고 생각했습니다. 어떻게 더 나은 / 더 효율적 / 등을 할 수 있습니까?
/// <summary>
/// Compare property values (as strings)
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public bool PropertiesEqual(object comparisonObject)
{
Type sourceType = this.GetType();
Type destinationType = comparisonObject.GetType();
if (sourceType == destinationType)
{
PropertyInfo[] sourceProperties = sourceType.GetProperties();
foreach (PropertyInfo pi in sourceProperties)
{
if ((sourceType.GetProperty(pi.Name).GetValue(this, null) == null && destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null) == null))
{
// if both are null, don't try to compare (throws exception)
}
else if (!(sourceType.GetProperty(pi.Name).GetValue(this, null).ToString() == destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null).ToString()))
{
// only need one property to be different to fail Equals.
return false;
}
}
}
else
{
throw new ArgumentException("Comparison object must be of the same type.","comparisonObject");
}
return true;
}
답변
단위 테스트 작성에 도움이되는 유사한 작업을 수행 할 코드 스 니펫을 찾고있었습니다. 다음은 내가 사용한 결과입니다.
public static bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
{
if (self != null && to != null)
{
Type type = typeof(T);
List<string> ignoreList = new List<string>(ignore);
foreach (System.Reflection.PropertyInfo pi in type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
{
if (!ignoreList.Contains(pi.Name))
{
object selfValue = type.GetProperty(pi.Name).GetValue(self, null);
object toValue = type.GetProperty(pi.Name).GetValue(to, null);
if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
{
return false;
}
}
}
return true;
}
return self == to;
}
편집하다:
위와 동일한 코드이지만 LINQ 및 Extension 메서드를 사용합니다.
public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
if (self != null && to != null)
{
var type = typeof(T);
var ignoreList = new List<string>(ignore);
var unequalProperties =
from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
where !ignoreList.Contains(pi.Name) && pi.GetUnderlyingType().IsSimpleType() && pi.GetIndexParameters().Length == 0
let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
let toValue = type.GetProperty(pi.Name).GetValue(to, null)
where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
select selfValue;
return !unequalProperties.Any();
}
return self == to;
}
public static class TypeExtensions
{
/// <summary>
/// Determine whether a type is simple (String, Decimal, DateTime, etc)
/// or complex (i.e. custom class with public properties and methods).
/// </summary>
/// <see cref="http://stackoverflow.com/questions/2442534/how-to-test-if-type-is-primitive"/>
public static bool IsSimpleType(
this Type type)
{
return
type.IsValueType ||
type.IsPrimitive ||
new[]
{
typeof(String),
typeof(Decimal),
typeof(DateTime),
typeof(DateTimeOffset),
typeof(TimeSpan),
typeof(Guid)
}.Contains(type) ||
(Convert.GetTypeCode(type) != TypeCode.Object);
}
public static Type GetUnderlyingType(this MemberInfo member)
{
switch (member.MemberType)
{
case MemberTypes.Event:
return ((EventInfo)member).EventHandlerType;
case MemberTypes.Field:
return ((FieldInfo)member).FieldType;
case MemberTypes.Method:
return ((MethodInfo)member).ReturnType;
case MemberTypes.Property:
return ((PropertyInfo)member).PropertyType;
default:
throw new ArgumentException
(
"Input MemberInfo must be if type EventInfo, FieldInfo, MethodInfo, or PropertyInfo"
);
}
}
}
답변
업데이트 : 최신 버전의 Compare-Net-Objects는 GitHub 에 있으며 NuGet 패키지 및 Tutorial이 있습니다. 다음과 같이 부를 수 있습니다.
//This is the comparison class
CompareLogic compareLogic = new CompareLogic();
ComparisonResult result = compareLogic.Compare(person1, person2);
//These will be different, write out the differences
if (!result.AreEqual)
Console.WriteLine(result.DifferencesString);
또는 일부 구성을 변경해야하는 경우
CompareLogic basicComparison = new CompareLogic()
{ Config = new ComparisonConfig()
{ MaxDifferences = propertyCount
//add other configurations
}
};
구성 가능한 매개 변수의 전체 목록은 ComparisonConfig.cs에 있습니다.
원래 답변 :
귀하의 코드에서 볼 수있는 제한 사항 :
-
가장 큰 것은 깊은 객체 비교를하지 않는다는 것입니다.
-
속성이 목록이거나 목록을 요소로 포함하는 경우 요소 별 비교를 수행하지 않습니다 (n 수준으로 갈 수 있음).
-
어떤 유형의 속성이 비교되어서는 안된다는 것을 고려하지 않습니다 (예 : PagedCollectionView 클래스의 속성과 같이 필터링 목적으로 사용되는 Func 속성).
-
실제로 어떤 속성이 다른지 추적하지 않습니다 (어설 션에 표시 할 수 있음).
나는 오늘 속성 깊은 비교를 통해 속성을 수행하기 위해 단위 테스트 목적으로 몇 가지 솔루션을 찾고 있었고 결국 http://comparenetobjects.codeplex.com을 사용했습니다 .
다음과 같이 간단히 사용할 수있는 하나의 클래스 만있는 무료 라이브러리입니다.
var compareObjects = new CompareObjects()
{
CompareChildren = true, //this turns deep compare one, otherwise it's shallow
CompareFields = false,
CompareReadOnly = true,
ComparePrivateFields = false,
ComparePrivateProperties = false,
CompareProperties = true,
MaxDifferences = 1,
ElementsToIgnore = new List<string>() { "Filter" }
};
Assert.IsTrue(
compareObjects.Compare(objectA, objectB),
compareObjects.DifferencesString
);
또한 Silverlight 용으로 쉽게 다시 컴파일 할 수 있습니다. 하나의 클래스를 Silverlight 프로젝트에 복사하고 개인 멤버 비교와 같이 Silverlight에서 사용할 수없는 비교를 위해 한두 줄의 코드를 제거하기 만하면됩니다.
답변
Override Object # Equals ()의 패턴을 따르는 것이 가장 좋을 것
같습니다. 더 나은 설명을 위해 : Bill Wagner의 효과적인 C # 읽기 -항목 9 내 생각에
public override Equals(object obOther)
{
if (null == obOther)
return false;
if (object.ReferenceEquals(this, obOther)
return true;
if (this.GetType() != obOther.GetType())
return false;
# private method to compare members.
return CompareMembers(this, obOther as ThisClass);
}
- 또한 동등성을 확인하는 메서드에서 true 또는 false를 반환해야합니다. 같거나 같지 않습니다. 예외를 던지는 대신 false를 반환합니다.
- Object # Equals 재정의를 고려할 것입니다.
- 이것을 고려 했음에도 불구하고 Reflection을 사용하여 속성을 비교하는 것은 아마도 느립니다 (이것을 백업 할 숫자가 없습니다). 이것은 C #의 valueType # Equals에 대한 기본 동작이며 값 형식에 대해 Equals를 재정의하고 성능에 대해 멤버 별 비교를 수행하는 것이 좋습니다. (이전에 사용자 지정 Property 개체 컬렉션이 있으므로 이것을 빨리 읽었습니다.
2011 년 12 월 업데이트 :
- 물론 유형에 이미 프로덕션 Equals ()가있는 경우 다른 접근 방식이 필요합니다.
- 테스트 목적으로 만 불변 데이터 구조를 비교하기 위해 이것을 사용하는 경우, Equals를 프로덕션 클래스에 추가해서는 안됩니다 (누군가 Equals 구현을 연결하여 테스트를 호스 할 수 있거나 프로덕션에 필요한 Equals 구현 생성을 방지 할 수 있음). .
답변
성능이 중요하지 않은 경우 직렬화하고 결과를 비교할 수 있습니다.
var serializer = new XmlSerializer(typeof(TheObjectType));
StringWriter serialized1 = new StringWriter(), serialized2 = new StringWriter();
serializer.Serialize(serialized1, obj1);
serializer.Serialize(serialized2, obj2);
bool areEqual = serialized1.ToString() == serialized2.ToString();
답변
나는 Big T 의 대답 이 꽤 좋았다고 생각하지만 깊은 비교가 누락되어 조금 조정했습니다.
using System.Collections.Generic;
using System.Reflection;
/// <summary>Comparison class.</summary>
public static class Compare
{
/// <summary>Compare the public instance properties. Uses deep comparison.</summary>
/// <param name="self">The reference object.</param>
/// <param name="to">The object to compare.</param>
/// <param name="ignore">Ignore property with name.</param>
/// <typeparam name="T">Type of objects.</typeparam>
/// <returns><see cref="bool">True</see> if both objects are equal, else <see cref="bool">false</see>.</returns>
public static bool PublicInstancePropertiesEqual<T>(T self, T to, params string[] ignore) where T : class
{
if (self != null && to != null)
{
var type = self.GetType();
var ignoreList = new List<string>(ignore);
foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (ignoreList.Contains(pi.Name))
{
continue;
}
var selfValue = type.GetProperty(pi.Name).GetValue(self, null);
var toValue = type.GetProperty(pi.Name).GetValue(to, null);
if (pi.PropertyType.IsClass && !pi.PropertyType.Module.ScopeName.Equals("CommonLanguageRuntimeLibrary"))
{
// Check of "CommonLanguageRuntimeLibrary" is needed because string is also a class
if (PublicInstancePropertiesEqual(selfValue, toValue, ignore))
{
continue;
}
return false;
}
if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
{
return false;
}
}
return true;
}
return self == to;
}
}
답변
복사 및 붙여 넣기 오류를 방지하기 위해 PublicInstancePropertiesEqual 메서드에 다음 줄을 추가합니다.
Assert.AreNotSame(self, to);
답변
속성에있는 모든 개체에서 .ToString ()을 재정의합니까? 그렇지 않으면 두 번째 비교가 null로 돌아올 수 있습니다.
또한 두 번째 비교에서 나는 지금부터 6 개월 / 2 년 후 가독성 측면에서 (A! = B)와 비교되는! (A == B)의 구조에 대해 울타리에 있습니다. 선 자체는 꽤 넓습니다. 모니터가 넓은 경우 괜찮지 만 잘 인쇄되지 않을 수 있습니다. (nitpick)
모든 개체가 항상이 코드가 작동하도록 속성을 사용하고 있습니까? 개체마다 다를 수 있지만 노출 된 모든 데이터는 동일 할 수있는 속성이없는 내부 데이터가있을 수 있습니까? 한 지점에서 같은 숫자에 부딪히지 만 두 개의 다른 정보 시퀀스를 생성하는 두 개의 난수 생성기와 같이 시간이 지남에 따라 변경 될 수있는 일부 데이터 또는 노출되지 않는 데이터를 생각하고 있습니다. 속성 인터페이스를 통해.