C #에서 개체 속성 비교 [닫기]


111

이것은 다른 많은 클래스에 상속 된 클래스에 대한 메서드로 제가 생각 해낸 것입니다. 아이디어는 동일한 유형의 객체 속성을 간단하게 비교할 수 있다는 것입니다.

자, 이것은 작동하지만 내 코드의 품질을 향상시키기 위해 조사를 위해 그것을 버릴 것이라고 생각했습니다. 어떻게 더 나은 / 더 효율적 / 등을 할 수 있습니까?

/// <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;
}


3
: 그런데이 SE 사이트를 알고 있나 codereview.stackexchange.com
WIP

몇 가지 객체 비교 라이브러리가 있습니다 : CompareNETObjects , DeepEqual , AutoCompare , ZCompare ...
nawfal

... 그리고 일부는 제네릭 같음 비교의 구현, 톤 : MemberwiseEqualityComparer , EQU , SemanticComparison , EqualityComparer , DeepEqualityComparer , 평등 , Equals.Fody . 후자의 그룹은 달성 할 수있는 범위와 유연성이 제한 될 수 있습니다.
nawfal

나는에 속하기 때문에 오프 주제로이 질문을 닫으 투표 해요 코드 리뷰
Xiaoy312

답변:


160

단위 테스트 작성에 도움이되는 유사한 작업을 수행 할 코드 스 니펫을 찾고있었습니다. 다음은 내가 사용한 결과입니다.

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"
               );
         }
      }
   }

빅 T - 아주 명곡하지만 확실히 .. 테스트 및 간단한 모두에게 큰 목적은 비교 역할을 감사 한
짐 tollan

1
이것은 좋지만 더 복잡한 개체에서는 작동하지 않는 것으로 나타났습니다. 예를 들어 문자열이있는 개체가 있지만 (잘 비교합니다)이 개체에는 올바르게 비교되지 않는 다른 개체 목록도 있으므로 어떻게 든 재귀해야합니다.
Ryan Thomas

1
다른 경우에는 예외를 발생시키는 인덱싱 된 속성을 제외해야하기 때문에 두 가지 기준이 더있는 첫 번째 기준에 추가해야했습니다. 이 오류의 기준은 다음과 같습니다. pi.GetIndexParameters (). Length == 0. @RyanThomas가 언급 한 문제를 해결하기위한 두 번째 기준은 pi.GetUnderlyingType (). IsSimpleType ()입니다. 보시다시피 IsSimpleType은 Type 클래스에 대해 존재하지 않는 확장입니다. 이 모든 조건과 확장을 추가하기 위해 대답을 수정했습니다.
Samuel

64

업데이트 : 최신 버전의 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에서 사용할 수없는 비교를 위해 한두 줄의 코드를 제거하기 만하면됩니다.


2
Liviu, Silverlight와 호환되지 않는 클래스에 대한 귀하의 의견을 확인했습니다. Silverlight 및 Windows Phone 7과 호환되도록 변경했습니다. 최신 정보를 얻으십시오. 에서 변경 세트 74131를 참조 comparenetobjects.codeplex.com/SourceControl/list/changesets
그렉 Finzer에게

이것은 유망 해 보입니다. 곧 그것을 밖으로 시도
DJ Burb

훌륭한 예를 들어 주셔서 감사합니다! 또한 IgnoreObjectTypes유형이 다른 경우 설정이 유용 할 수 있습니다.
Sergey Brunov

버전 2.0에는 Silverlight 5+, Windows Phone 8+, WinRT 8+, Xamarin IOS 및 Xamarin Droid와 호환되는 Portable Class Library 버전이 있습니다
Greg Finzer 2014 년

DifferencesStringCompareObjects 클래스에서 더 이상 사용되지 않습니다. 하지만 지금은 대신 ComparisonResult으로부터 얻을 수 있습니다 :var r = compareObjects.Compare(objectA, objectB); Assert.IsTrue(r.AreEqual, r.DifferencesString);
마리아노 Desanze

6

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 구현 생성을 방지 할 수 있음). .

상속되는 기본 클래스에서 이것을 구현하려고하기 때문에 .Equals () 재정의하는 데 문제가 발생했습니다 ...이 클래스에 대한 키를 모르기 때문에 실행할 수 없습니다. GetHasCode ()에 대한 적절한 재정의를 구현합니다 (Equals ()를 재정의 할 때 필요).
nailitdown

요구 사항은 objA.Equals (objB)이면 objA.GetHashCode () == objB.GetHashCode ()입니다. GetHashCode는 클래스의 변경 가능한 상태 / 데이터에 의존해서는 안됩니다. 클래스의 키가 의미하는 바를 이해하지 못했습니다. 해결할 수있는 것 같습니다. 기본 유형에 '키'가 없습니까?
Gishu

6

성능이 중요하지 않은 경우 직렬화하고 결과를 비교할 수 있습니다.

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();

4
전에 이것을 시도, 당신은 얼마나 많은 객체가 직렬화되지 않는지 궁금 할 것입니다 ...
Offler dec

5

나는 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;
    }
}

4

복사 및 붙여 넣기 오류를 방지하기 위해 PublicInstancePropertiesEqual 메서드에 다음 줄을 추가합니다.

Assert.AreNotSame(self, to);

2

속성에있는 모든 개체에서 .ToString ()을 재정의합니까? 그렇지 않으면 두 번째 비교가 null로 돌아올 수 있습니다.

또한 두 번째 비교에서 나는 지금부터 6 개월 / 2 년 후 가독성 측면에서 (A! = B)와 비교되는! (A == B)의 구조에 대해 울타리에 있습니다. 선 자체는 꽤 넓습니다. 모니터가 넓은 경우 괜찮지 만 잘 인쇄되지 않을 수 있습니다. (nitpick)

모든 개체가 항상이 코드가 작동하도록 속성을 사용하고 있습니까? 개체마다 다를 수 있지만 노출 된 모든 데이터는 동일 할 수있는 속성이없는 내부 데이터가있을 수 있습니까? 한 지점에서 같은 숫자에 부딪히지 만 두 개의 다른 정보 시퀀스를 생성하는 두 개의 난수 생성기와 같이 시간이 지남에 따라 변경 될 수있는 일부 데이터 또는 노출되지 않는 데이터를 생각하고 있습니다. 속성 인터페이스를 통해.


좋은 점-! = ... 동의, 점을 찍었습니다. ToString ()은 객체를 반환하는 .GetValue를 해결하려는 시도였습니다 (따라서 비교는 참조 비교이므로 항상 false입니다) .. 더 좋은 방법이 있습니까?
nailitdown

GetValue가 객체를 반환하는 경우이 함수를 다시 반복 할 수 있습니까? 즉, 반환 된 객체에 대해 PropertiesEqual을 호출합니까?
mmr

1

같은 유형의 객체 만 비교하거나 상속 체인 아래로 내려가는 경우 객체가 아닌 기본 유형으로 매개 변수를 지정하지 않는 이유는 무엇입니까?

또한 매개 변수에 대한 널 검사도 수행하십시오.

또한 코드를 더 읽기 쉽게 만들기 위해 'var'를 사용합니다 (C # 3 코드 인 경우).

또한 객체에 속성으로 참조 유형이 있으면 실제로 값을 비교하지 않는 ToString ()을 호출하는 것입니다. ToString이 재정의되지 않으면 형식 이름을 문자열로 반환하여 거짓 긍정을 반환 할 수 있습니다.


참조 유형에 대한 좋은 점-제 경우에는 중요하지 않지만 좋은 기회가 있습니다.
nailitdown

1

내가 제안하는 첫 번째 것은 실제 비교를 분할하여 좀 더 읽기 쉽게하는 것입니다 (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을 구현해야합니다. :-).


그냥 IComparable을보고 있었지만 정렬과 정렬을위한 것 같았습니다. 두 개체의 동등성을 비교하는 데 정말 유용합니까?
nailitdown

.Equals (object o)는 this.CompareTo (o) == 0으로 정의되기 때문입니다. 따라서 equals는 ComparesTo ()를 사용하여 동등성을 결정합니다. 이것은 반사를 사용하는 것보다 훨씬 더 효율적일 것입니다.
tsimon

Equals가 CompareTo ()를 참조하여 구현되거나 구현되어야한다고 착각 할 수 있습니다. 여기에 설명 된대로 Equals 재정의를 고려해야합니다. stackoverflow.com/questions/104158/…
tsimon

1

여기에 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;
        }

깊은 개체 그래프가있는 경우 변경된 이전 속성과 새 속성 목록을 반환하기 위해 위에서 사용하는 가장 좋은 방법은 무엇입니까?
Rod

1

나는 이것을 끝냈다.

    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"))

1

유형 당 한 번만 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;
}

1

완전성을 위해 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/가 있습니다. CompareNETObjects 및 구성 할 여러 옵션.


1

개체가 아닌지 확인하십시오 null.

obj1obj2:

if(obj1 == null )
{
   return false;
}
return obj1.Equals( obj2 );

둘 다 null이면 어떨까요? 그렇다면 그들은 같지 않습니까?
mmr

내 경우에는 .Equals ()를 사용하는 것이 작동하지 않는 것 같습니다. 이것이
제가이

글쎄, 내가 테스트하는 경우는 세션에서 하나, 새로 생성 된 두 개의 객체입니다. 둘을 .Equals ()와 비교하면 둘 다 동일한 속성 값이 있더라도 false를 반환합니다.
nailitdown

0

이것은 개체가 다른 경우에도 작동합니다. 유틸리티 클래스의 메소드를 사용자 정의 할 수 있으며 개인 속성도 비교하고 싶을 수도 있습니다.

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;
    }


}

이 코드는 100 % 효율적이지 않습니다. 예를 들어 개체 유형의 속성이 포함 된 경우 일부 상황에서는 작동하지 않습니다.
Tono Nam

0

위의 Liviu 답변 업데이트-CompareObjects.DifferencesString은 더 이상 사용되지 않습니다.

이것은 단위 테스트에서 잘 작동합니다.

CompareLogic compareLogic = new CompareLogic();
ComparisonResult result = compareLogic.Compare(object1, object2);
Assert.IsTrue(result.AreEqual);

1
비방을 고쳐서 기쁘지만이 답변은 실제로 Liviu의 답변에있는 주석이어야한다고 생각합니다. 특히 Liviu와 비교하여 샘플 코드 에는 CompareLogic의 매개 변수 (중요하다고 확신합니다)와 assert 메시지 (사용되지 않는 메시지 ) 없기 때문 입니다. 주장은 다음과 같이 수정할 수 있습니다.Assert.IsTrue(result.AreEqual, result.DifferencesString);
Mariano Desanze 2014 년

0

이 메서드는 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)


0

@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;
}

0

때로는 모든 공용 속성을 비교하고 싶지 않고 그 하위 집합 만 비교하고 싶을 때가 있습니다.이 경우 논리를 이동하여 원하는 속성 목록을 추상 클래스와 비교할 수 있습니다.

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 };
    }
}

0

내 솔루션은 위의 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");
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.