[c#] 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 및 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)

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