C #에서 개체 속성 비교
이것은 다른 많은 클래스에서 상속받은 클래스의 메소드로 생각해 낸 것입니다. 아이디어는 동일한 유형의 객체 속성을 간단하게 비교할 수 있다는 것입니다.
이제는 효과가 있지만 코드의 품질을 향상시키기 위해 조사를 위해 그것을 버릴 것이라고 생각했습니다. 어떻게 더 좋고 더 효율적일 수 있습니까?
/// <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 및 확장 방법을 사용합니다.
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에서 사용할 수없는 비교를 위해 하나 또는 두 줄의 코드를 제거하십시오.
나는 재정의 개체 번호를 같음에 대한 패턴 () 따라 가장 좋은 것입니다 생각
더 나은에 대한 설명 : 읽기 빌 와그너의 효과적인 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를 리턴해야합니다. 그것들이 같거나 그렇지 않다. 예외를 던지는 대신에 거짓을 돌려 준다.
- 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)의 구성에 대해 울타리에 있습니다. 라인 자체는 꽤 넓습니다. 넓은 모니터를 가지고 있지만 잘 인쇄되지 않을 수 있습니다. (니트 픽)
이 코드가 작동하도록 모든 객체가 항상 속성을 사용합니까? 개체마다 다를 수있는 내부의 비 속성 데이터가있을 수 있지만 노출 된 모든 데이터는 동일합니까? 한 시점에서 같은 숫자에 도달하는 두 개의 난수 생성기와 같이 시간이 지남에 따라 변경 될 수있는 일부 데이터를 생각하고 있지만 두 개의 다른 정보 시퀀스 또는 노출되지 않은 데이터를 생성하려고합니다. 속성 인터페이스를 통해.
동일한 유형의 객체 또는 상속 체인을 더 많이 비교하는 경우 객체가 아닌 기본 유형으로 매개 변수를 지정하십시오.
또한 매개 변수에 대한 널 점검도 수행하십시오.
또한 코드를 더 읽기 쉽게 만들기 위해 'var'을 사용합니다 (C # 3 코드 인 경우)
또한 객체에 속성으로 참조 유형이있는 경우 실제로 값을 비교하지 않는 ToString ()을 호출합니다. ToString을 재정의하지 않으면 형식 이름을 문자열로 반환하여 거짓 양성을 반환 할 수 있습니다.
내가 제안하는 첫 번째 것은 실제 비교를 분할하여 좀 더 읽기 쉽도록하는 것입니다 (ToString ()도 가져 왔습니다-필요합니까?).
else {
object originalProperty = sourceType.GetProperty(pi.Name).GetValue(this, null);
object comparisonProperty = destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null);
if (originalProperty != comparisonProperty)
return false;
다음 제안은 가능한 한 반사 사용을 최소화하는 것입니다. 정말 느립니다. 내 말은, 정말 천천히. 이 작업을 수행하려면 속성 참조를 캐싱하는 것이 좋습니다. 리플렉션 API에 친숙하지 않으므로 약간 벗어난 경우 컴파일되도록 조정하십시오.
// elsewhere
Dictionary<object, Property[]> lookupDictionary = new Dictionary<object, Property[]>;
Property[] objectProperties = null;
if (lookupDictionary.ContainsKey(sourceType)) {
objectProperties = lookupProperties[sourceType];
} else {
// build array of Property references
PropertyInfo[] sourcePropertyInfos = sourceType.GetProperties();
Property[] sourceProperties = new Property[sourcePropertyInfos.length];
for (int i=0; i < sourcePropertyInfos.length; i++) {
sourceProperties[i] = sourceType.GetProperty(pi.Name);
}
// add to cache
objectProperties = sourceProperties;
lookupDictionary[object] = sourceProperties;
}
// loop through and compare against the instances
그러나 나는 다른 포스터에 동의한다고 말해야합니다. 이것은 게으르고 비효율적입니다. 대신 :-) 대신 IComparable을 구현해야합니다.
여기서는 null = null을 동일하게 취급하도록 수정되었습니다.
private 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 (PropertyInfo pi in type.GetProperties(BindingFlags.Public | 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 != null)
{
if (!selfValue.Equals(toValue))
return false;
}
else if (toValue != null)
return false;
}
}
return true;
}
return self == to;
}
나는 이것을 끝내었다.
public static string ToStringNullSafe(this object obj)
{
return obj != null ? obj.ToString() : String.Empty;
}
public static bool Compare<T>(T a, T b)
{
int count = a.GetType().GetProperties().Count();
string aa, bb;
for (int i = 0; i < count; i++)
{
aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe();
bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe();
if (aa != bb)
{
return false;
}
}
return true;
}
용법:
if (Compare<ObjectType>(a, b))
최신 정보
이름으로 일부 속성을 무시하려는 경우 :
public static string ToStringNullSafe(this object obj)
{
return obj != null ? obj.ToString() : String.Empty;
}
public static bool Compare<T>(T a, T b, params string[] ignore)
{
int count = a.GetType().GetProperties().Count();
string aa, bb;
for (int i = 0; i < count; i++)
{
aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe();
bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe();
if (aa != bb && ignore.Where(x => x == a.GetType().GetProperties()[i].Name).Count() == 0)
{
return false;
}
}
return true;
}
용법:
if (MyFunction.Compare<ObjType>(a, b, "Id","AnotherProp"))
유형 당 한 번만 GetProperties를 호출하여 코드를 최적화 할 수 있습니다.
public static string ToStringNullSafe(this object obj)
{
return obj != null ? obj.ToString() : String.Empty;
}
public static bool Compare<T>(T a, T b, params string[] ignore)
{
var aProps = a.GetType().GetProperties();
var bProps = b.GetType().GetProperties();
int count = aProps.Count();
string aa, bb;
for (int i = 0; i < count; i++)
{
aa = aProps[i].GetValue(a, null).ToStringNullSafe();
bb = bProps[i].GetValue(b, null).ToStringNullSafe();
if (aa != bb && ignore.Where(x => x == aProps[i].Name).Count() == 0)
{
return false;
}
}
return true;
}
완전성을 위해 http://www.cyotek.com/blog/comparing-the-properties-of-two-objects-via-reflection 에 대한 참조를 추가하고 싶습니다 .이 페이지의 다른 답변보다 더 완벽한 논리가 있습니다.
그러나 나는 Compare-Net-Objects 라이브러리를 선호합니다 https://github.com/GregFinzer/Compare-Net-Objects ( Liviu Trifoi 의 답변 참조 )
라이브러리에는 NuGet 패키지가 있습니다 http://www.nuget.org/packages/ NETObject 와 구성 할 여러 옵션을 비교합니다.
객체가 아닌지 확인하십시오 null
.
가지고 obj1
있고 obj2
:
if(obj1 == null )
{
return false;
}
return obj1.Equals( obj2 );
객체가 다른 경우에도 작동합니다. 유틸리티 클래스에서 메소드를 사용자 정의하여 개인 특성을 비교할 수도 있습니다 ...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
class ObjectA
{
public string PropertyA { get; set; }
public string PropertyB { get; set; }
public string PropertyC { get; set; }
public DateTime PropertyD { get; set; }
public string FieldA;
public DateTime FieldB;
}
class ObjectB
{
public string PropertyA { get; set; }
public string PropertyB { get; set; }
public string PropertyC { get; set; }
public DateTime PropertyD { get; set; }
public string FieldA;
public DateTime FieldB;
}
class Program
{
static void Main(string[] args)
{
// create two objects with same properties
ObjectA a = new ObjectA() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" };
ObjectB b = new ObjectB() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" };
// add fields to those objects
a.FieldA = "hello";
b.FieldA = "Something differnt";
if (a.ComparePropertiesTo(b))
{
Console.WriteLine("objects have the same properties");
}
else
{
Console.WriteLine("objects have diferent properties!");
}
if (a.CompareFieldsTo(b))
{
Console.WriteLine("objects have the same Fields");
}
else
{
Console.WriteLine("objects have diferent Fields!");
}
Console.Read();
}
}
public static class Utilities
{
public static bool ComparePropertiesTo(this Object a, Object b)
{
System.Reflection.PropertyInfo[] properties = a.GetType().GetProperties(); // get all the properties of object a
foreach (var property in properties)
{
var propertyName = property.Name;
var aValue = a.GetType().GetProperty(propertyName).GetValue(a, null);
object bValue;
try // try to get the same property from object b. maybe that property does
// not exist!
{
bValue = b.GetType().GetProperty(propertyName).GetValue(b, null);
}
catch
{
return false;
}
if (aValue == null && bValue == null)
continue;
if (aValue == null && bValue != null)
return false;
if (aValue != null && bValue == null)
return false;
// if properties do not match return false
if (aValue.GetHashCode() != bValue.GetHashCode())
{
return false;
}
}
return true;
}
public static bool CompareFieldsTo(this Object a, Object b)
{
System.Reflection.FieldInfo[] fields = a.GetType().GetFields(); // get all the properties of object a
foreach (var field in fields)
{
var fieldName = field.Name;
var aValue = a.GetType().GetField(fieldName).GetValue(a);
object bValue;
try // try to get the same property from object b. maybe that property does
// not exist!
{
bValue = b.GetType().GetField(fieldName).GetValue(b);
}
catch
{
return false;
}
if (aValue == null && bValue == null)
continue;
if (aValue == null && bValue != null)
return false;
if (aValue != null && bValue == null)
return false;
// if properties do not match return false
if (aValue.GetHashCode() != bValue.GetHashCode())
{
return false;
}
}
return true;
}
}
위의 Liviu의 답변 업데이트-CompareObjects.DifferencesString은 더 이상 사용되지 않습니다.
이것은 단위 테스트에서 잘 작동합니다.
CompareLogic compareLogic = new CompareLogic();
ComparisonResult result = compareLogic.Compare(object1, object2);
Assert.IsTrue(result.AreEqual);
이 메소드는 properties
클래스 를 가져 와서 각각의 값을 비교합니다 property
. 값이 다르면 그럴 것이고 return false
그렇지 않을 것 return true
입니다.
public static bool Compare<T>(T Object1, T object2)
{
//Get the type of the object
Type type = typeof(T);
//return false if any of the object is false
if (Object1 == null || object2 == null)
return false;
//Loop through each properties inside class and get values for the property from both the objects and compare
foreach (System.Reflection.PropertyInfo property in type.GetProperties())
{
if (property.Name != "ExtensionData")
{
string Object1Value = string.Empty;
string Object2Value = string.Empty;
if (type.GetProperty(property.Name).GetValue(Object1, null) != null)
Object1Value = type.GetProperty(property.Name).GetValue(Object1, null).ToString();
if (type.GetProperty(property.Name).GetValue(object2, null) != null)
Object2Value = type.GetProperty(property.Name).GetValue(object2, null).ToString();
if (Object1Value.Trim() != Object2Value.Trim())
{
return false;
}
}
}
return true;
}
용법:
bool isEqual = Compare<Employee>(Object1, Object2)
@nawfal : s 답변을 확장하기 위해 이것을 사용하여 동일한 속성 이름을 비교하기 위해 단위 테스트에서 다른 유형의 객체를 테스트합니다. 제 경우에는 데이터베이스 엔터티와 DTO입니다.
내 테스트에서 이와 같이 사용되었습니다.
Assert.IsTrue(resultDto.PublicInstancePropertiesEqual(expectedEntity));
public static bool PublicInstancePropertiesEqual<T, Z>(this T self, Z to, params string[] ignore) where T : class
{
if (self != null && to != null)
{
var type = typeof(T);
var type2 = typeof(Z);
var ignoreList = new List<string>(ignore);
var unequalProperties =
from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
where !ignoreList.Contains(pi.Name)
let selfValue = type.GetProperty(pi.Name).GetValue(self, null)
let toValue = type2.GetProperty(pi.Name).GetValue(to, null)
where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))
select selfValue;
return !unequalProperties.Any();
}
return self == null && to == null;
}
때로는 모든 공개 속성을 비교하고 싶지 않고 하위 집합 만 비교하고 싶기 때문에이 경우 원하는 속성 목록을 추상 클래스와 비교하기 위해 로직을 이동하면됩니다.
public abstract class ValueObject<T> where T : ValueObject<T>
{
protected abstract IEnumerable<object> GetAttributesToIncludeInEqualityCheck();
public override bool Equals(object other)
{
return Equals(other as T);
}
public bool Equals(T other)
{
if (other == null)
{
return false;
}
return GetAttributesToIncludeInEqualityCheck()
.SequenceEqual(other.GetAttributesToIncludeInEqualityCheck());
}
public static bool operator ==(ValueObject<T> left, ValueObject<T> right)
{
return Equals(left, right);
}
public static bool operator !=(ValueObject<T> left, ValueObject<T> right)
{
return !(left == right);
}
public override int GetHashCode()
{
int hash = 17;
foreach (var obj in this.GetAttributesToIncludeInEqualityCheck())
hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode());
return hash;
}
}
나중에이 추상 클래스를 사용하여 객체를 비교하십시오.
public class Meters : ValueObject<Meters>
{
...
protected decimal DistanceInMeters { get; private set; }
...
protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck()
{
return new List<Object> { DistanceInMeters };
}
}
내 솔루션은 위의 Aras Alenin 답변에서 영감을 얻어 한 수준의 객체 비교와 사용자 정의 객체를 추가하여 비교 결과를 얻었습니다. 또한 객체 이름으로 속성 이름을 얻는 데 관심이 있습니다.
public static IEnumerable<ObjectPropertyChanged> GetPublicSimplePropertiesChanged<T>(this T previous, T proposedChange,
string[] namesOfPropertiesToBeIgnored) where T : class
{
return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, true, null, null);
}
public static IReadOnlyList<ObjectPropertyChanged> GetPublicGenericPropertiesChanged<T>(this T previous, T proposedChange,
string[] namesOfPropertiesToBeIgnored) where T : class
{
return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, false, null, null);
}
/// <summary>
/// Gets the names of the public properties which values differs between first and second objects.
/// Considers 'simple' properties AND for complex properties without index, get the simple properties of the children objects.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="previous">The previous object.</param>
/// <param name="proposedChange">The second object which should be the new one.</param>
/// <param name="namesOfPropertiesToBeIgnored">The names of the properties to be ignored.</param>
/// <param name="simpleTypeOnly">if set to <c>true</c> consider simple types only.</param>
/// <param name="parentTypeString">The parent type string. Meant only for recursive call with simpleTypeOnly set to <c>true</c>.</param>
/// <param name="secondType">when calling recursively, the current type of T must be clearly defined here, as T will be more generic (using base class).</param>
/// <returns>
/// the names of the properties
/// </returns>
private static IReadOnlyList<ObjectPropertyChanged> GetPublicGenericPropertiesChanged<T>(this T previous, T proposedChange,
string[] namesOfPropertiesToBeIgnored, bool simpleTypeOnly, string parentTypeString, Type secondType) where T : class
{
List<ObjectPropertyChanged> propertiesChanged = new List<ObjectPropertyChanged>();
if (previous != null && proposedChange != null)
{
var type = secondType == null ? typeof(T) : secondType;
string typeStr = parentTypeString + type.Name + ".";
var ignoreList = namesOfPropertiesToBeIgnored.CreateList();
IEnumerable<IEnumerable<ObjectPropertyChanged>> genericPropertiesChanged =
from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
where !ignoreList.Contains(pi.Name) && pi.GetIndexParameters().Length == 0
&& (!simpleTypeOnly || simpleTypeOnly && pi.PropertyType.IsSimpleType())
let firstValue = type.GetProperty(pi.Name).GetValue(previous, null)
let secondValue = type.GetProperty(pi.Name).GetValue(proposedChange, null)
where firstValue != secondValue && (firstValue == null || !firstValue.Equals(secondValue))
let subPropertiesChanged = simpleTypeOnly || pi.PropertyType.IsSimpleType()
? null
: GetPublicGenericPropertiesChanged(firstValue, secondValue, namesOfPropertiesToBeIgnored, true, typeStr, pi.PropertyType)
let objectPropertiesChanged = subPropertiesChanged != null && subPropertiesChanged.Count() > 0
? subPropertiesChanged
: (new ObjectPropertyChanged(proposedChange.ToString(), typeStr + pi.Name, firstValue.ToStringOrNull(), secondValue.ToStringOrNull())).CreateList()
select objectPropertiesChanged;
if (genericPropertiesChanged != null)
{ // get items from sub lists
genericPropertiesChanged.ForEach(a => propertiesChanged.AddRange(a));
}
}
return propertiesChanged;
}
다음 클래스를 사용하여 비교 결과 저장
[System.Serializable]
public class ObjectPropertyChanged
{
public ObjectPropertyChanged(string objectId, string propertyName, string previousValue, string changedValue)
{
ObjectId = objectId;
PropertyName = propertyName;
PreviousValue = previousValue;
ProposedChangedValue = changedValue;
}
public string ObjectId { get; set; }
public string PropertyName { get; set; }
public string PreviousValue { get; set; }
public string ProposedChangedValue { get; set; }
}
그리고 샘플 단위 테스트 :
[TestMethod()]
public void GetPublicGenericPropertiesChangedTest1()
{
// Define objects to test
Function func1 = new Function { Id = 1, Description = "func1" };
Function func2 = new Function { Id = 2, Description = "func2" };
FunctionAssignment funcAss1 = new FunctionAssignment
{
Function = func1,
Level = 1
};
FunctionAssignment funcAss2 = new FunctionAssignment
{
Function = func2,
Level = 2
};
// Main test: read properties changed
var propertiesChanged = Utils.GetPublicGenericPropertiesChanged(funcAss1, funcAss2, null);
Assert.IsNotNull(propertiesChanged);
Assert.IsTrue(propertiesChanged.Count == 3);
Assert.IsTrue(propertiesChanged[0].PropertyName == "FunctionAssignment.Function.Description");
Assert.IsTrue(propertiesChanged[1].PropertyName == "FunctionAssignment.Function.Id");
Assert.IsTrue(propertiesChanged[2].PropertyName == "FunctionAssignment.Level");
}
참고 URL : https://stackoverflow.com/questions/506096/comparing-object-properties-in-c-sharp
'program story' 카테고리의 다른 글
안드로이드에서 UI 스레드를 감지하는 방법? (0) | 2020.07.28 |
---|---|
dplyr을 사용하여 테이블의 모든 행에 함수를 적용 하시겠습니까? (0) | 2020.07.28 |
MySQL-하나의 쿼리에서 다른 값으로 여러 행 업데이트 (0) | 2020.07.28 |
UIView를 강제로 다시 그리는 가장 강력한 방법은 무엇입니까? (0) | 2020.07.28 |
새로운 대기열에 자동으로 기존 값을 대기열에 넣는 고정 크기 대기열 (0) | 2020.07.28 |