두 객체의 속성을 비교하여 차이점을 찾으십니까?


155

유형이 같은 두 개의 객체가 있으며 각 객체의 공용 속성을 반복하고 일치하지 않는 속성에 대해 사용자에게 경고하고 싶습니다.

객체에 어떤 속성이 포함되어 있는지 모른 채이 작업을 수행 할 수 있습니까?


나는 이것이 당신을 도울 것이라고 생각합니다 ... 속성과 값을 통해 반복
Damien Doumer

나는 이것이 당신을 도울 것이라고 생각합니다. 객체의 속성과 값을 반복합니다 .
Damien Doumer

답변:


212

그렇습니다 Equals. 각 속성 유형이 적절하게 구현되었다고 가정합니다 . 대안은 ReflectiveEquals알려진 유형을 제외한 모든 유형에 대해 재귀 적 으로 사용 하는 것이지만 까다로워집니다.

public bool ReflectiveEquals(object first, object second)
{
    if (first == null && second == null)
    {
        return true;
    }
    if (first == null || second == null)
    {
        return false;
    }
    Type firstType = first.GetType();
    if (second.GetType() != firstType)
    {
        return false; // Or throw an exception
    }
    // This will only use public properties. Is that enough?
    foreach (PropertyInfo propertyInfo in firstType.GetProperties())
    {
        if (propertyInfo.CanRead)
        {
            object firstValue = propertyInfo.GetValue(first, null);
            object secondValue = propertyInfo.GetValue(second, null);
            if (!object.Equals(firstValue, secondValue))
            {
                return false;
            }
        }
    }
    return true;
}

이 방법으로 재귀를 사용하고 객체가 가질 수있는 모든 컬렉션을 비교할 수 있습니까? 예 : Object1-> 학교 목록-> 수업 목록-> 학생 목록
Peter PitLock

@ PetetPitLock : 컬렉션에 대해 다른 처리를 원할 것입니다. 목록의 속성을 비교하는 것만으로는 효과가 없습니다.
Jon Skeet

2
고마워 jon, MasterObject (MO)와 LightweightMasterObject (LWMO)가 있는데 MasterObject의 제거 된 버전이지만 둘 다 컬렉션이 있습니다. 재귀와 함께 제공된 코드를 사용할 수 있는지 확인하려고합니다-LWMO가 비어 있습니다. 시작할 때, 그러나 MO의 각 컬렉션과 해당 속성을 통과 할 때 해당 LWMO의 값이 설정됩니다.이 구현은 제공된 코드에서 재귀를 허용합니까?
Peter PitLock

@ PetetPitLock : 기본적 으로이 시점에서 새로운 질문을 해야하는 것처럼 들립니다. 기본적 으로이 답변에 대한 질문은 요구 사항에 충분히 가깝지 않습니다.
Jon Skeet

42

물론 당신은 반사와 함께 할 수 있습니다. 주어진 유형에서 속성을 가져 오는 코드는 다음과 같습니다.

var info = typeof(SomeType).GetProperties();

당신이 속성에 대해 비교하고있는 것에 대해 더 많은 정보를 제공 할 수 있다면 우리는 기본 diffing 알고리즘을 만들 수 있습니다. 이 코드는 이름에 따라 다릅니다.

public bool AreDifferent(Type t1, Type t2) {
  var list1 = t1.GetProperties().OrderBy(x => x.Name).Select(x => x.Name);
  var list2 = t2.GetProperties().OrderBy(x => x.Name).Select(x => x.Name);
  return list1.SequenceEqual(list2);
}

그는 이 일치하지 않는 동일한 유형의 두 객체를 의미한다고 생각 합니다.
BFree

@JaredPar : Diffing이 작동하지 않습니다. 유형 자체가 아닌 한 PropertyInfo 객체는 확실히 동일하지 않습니다.
Mehrdad Afshari

@Mehrdad, 내 이름의 기본 예일뿐입니다. 좀 더 구체적으로 만들기 전에 그들이 찾던 것을 명확하게하기 위해 OP를 기다리고있었습니다.
JaredPar

1
@JaredPar : 이해하지만 실제로는 이름이 작동하지 않습니다. 아이디어를 전달할 수도 있지만 약간 오해의 소지가 있습니다. 순서는 어쨌든 동일하지 않습니다. 나는.Select(...)
Mehrdad Afshari를

죄송합니다. 속성의 값이 다른 곳을 의미했습니다. 감사합니다
Gavin

7

나는 이것이 과잉 일 것임을 알고 있지만,이 목적을 위해 사용하는 ObjectComparer 클래스는 다음과 같습니다.

/// <summary>
/// Utility class for comparing objects.
/// </summary>
public static class ObjectComparer
{
    /// <summary>
    /// Compares the public properties of any 2 objects and determines if the properties of each
    /// all contain the same value.
    /// <para> 
    /// In cases where object1 and object2 are of different Types (both being derived from Type T) 
    /// we will cast both objects down to the base Type T to ensure the property comparison is only 
    /// completed on COMMON properties.
    /// (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo --
    /// both objects will be cast to Foo for comparison)
    /// </para>
    /// </summary>
    /// <typeparam name="T">Any class with public properties.</typeparam>
    /// <param name="object1">Object to compare to object2.</param>
    /// <param name="object2">Object to compare to object1.</param>
    /// <param name="propertyInfoList">A List of <see cref="PropertyInfo"/> objects that contain data on the properties
    /// from object1 that are not equal to the corresponding properties of object2.</param>
    /// <returns>A boolean value indicating whether or not the properties of each object match.</returns>
    public static bool GetDifferentProperties<T> ( T object1 , T object2 , out List<PropertyInfo> propertyInfoList )
        where T : class
    {
        return GetDifferentProperties<T>( object1 , object2 , null , out propertyInfoList );
    }

    /// <summary>
    /// Compares the public properties of any 2 objects and determines if the properties of each
    /// all contain the same value.
    /// <para> 
    /// In cases where object1 and object2 are of different Types (both being derived from Type T) 
    /// we will cast both objects down to the base Type T to ensure the property comparison is only 
    /// completed on COMMON properties.
    /// (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo --
    /// both objects will be cast to Foo for comparison)
    /// </para>
    /// </summary>
    /// <typeparam name="T">Any class with public properties.</typeparam>
    /// <param name="object1">Object to compare to object2.</param>
    /// <param name="object2">Object to compare to object1.</param>
    /// <param name="ignoredProperties">A list of <see cref="PropertyInfo"/> objects
    /// to ignore when completing the comparison.</param>
    /// <param name="propertyInfoList">A List of <see cref="PropertyInfo"/> objects that contain data on the properties
    /// from object1 that are not equal to the corresponding properties of object2.</param>
    /// <returns>A boolean value indicating whether or not the properties of each object match.</returns>
    public static bool GetDifferentProperties<T> ( T object1 , T object2 , List<PropertyInfo> ignoredProperties , out List<PropertyInfo> propertyInfoList )
        where T : class
    {
        propertyInfoList = new List<PropertyInfo>();

        // If either object is null, we can't compare anything
        if ( object1 == null || object2 == null )
        {
            return false;
        }

        Type object1Type = object1.GetType();
        Type object2Type = object2.GetType();

        // In cases where object1 and object2 are of different Types (both being derived from Type T) 
        // we will cast both objects down to the base Type T to ensure the property comparison is only 
        // completed on COMMON properties.
        // (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo --
        // both objects will be cast to Foo for comparison)
        if ( object1Type != object2Type )
        {
            object1Type = typeof ( T );
            object2Type = typeof ( T );
        }

        // Remove any properties to be ignored
        List<PropertyInfo> comparisonProps =
            RemoveProperties( object1Type.GetProperties() , ignoredProperties );

        foreach ( PropertyInfo object1Prop in comparisonProps )
        {
            Type propertyType = null;
            object object1PropValue = null;
            object object2PropValue = null;

            // Rule out an attempt to check against a property which requires
            // an index, such as one accessed via this[]
            if ( object1Prop.GetIndexParameters().GetLength( 0 ) == 0 )
            {
                // Get the value of each property
                object1PropValue = object1Prop.GetValue( object1 , null );
                object2PropValue = object2Type.GetProperty( object1Prop.Name ).GetValue( object2 , null );

                // As we are comparing 2 objects of the same type we know
                // that they both have the same properties, so grab the
                // first non-null value
                if ( object1PropValue != null )
                    propertyType = object1PropValue.GetType().GetInterface( "IComparable" );

                if ( propertyType == null )
                    if ( object2PropValue != null )
                        propertyType = object2PropValue.GetType().GetInterface( "IComparable" );
            }

            // If both objects have null values or were indexed properties, don't continue
            if ( propertyType != null )
            {
                // If one property value is null and the other is not null, 
                // they aren't equal; this is done here as a native CompareTo
                // won't work with a null value as the target
                if ( object1PropValue == null || object2PropValue == null )
                {
                    propertyInfoList.Add( object1Prop );
                }
                else
                {
                    // Use the native CompareTo method
                    MethodInfo nativeCompare = propertyType.GetMethod( "CompareTo" );

                    // Sanity Check:
                    // If we don't have a native CompareTo OR both values are null, we can't compare;
                    // hence, we can't confirm the values differ... just go to the next property
                    if ( nativeCompare != null )
                    {
                        // Return the native CompareTo result
                        bool equal = ( 0 == (int) ( nativeCompare.Invoke( object1PropValue , new object[] {object2PropValue} ) ) );

                        if ( !equal )
                        {
                            propertyInfoList.Add( object1Prop );
                        }
                    }
                }
            }
        }
        return propertyInfoList.Count == 0;
    }

    /// <summary>
    /// Compares the public properties of any 2 objects and determines if the properties of each
    /// all contain the same value.
    /// <para> 
    /// In cases where object1 and object2 are of different Types (both being derived from Type T) 
    /// we will cast both objects down to the base Type T to ensure the property comparison is only 
    /// completed on COMMON properties.
    /// (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo --
    /// both objects will be cast to Foo for comparison)
    /// </para>
    /// </summary>
    /// <typeparam name="T">Any class with public properties.</typeparam>
    /// <param name="object1">Object to compare to object2.</param>
    /// <param name="object2">Object to compare to object1.</param>
    /// <returns>A boolean value indicating whether or not the properties of each object match.</returns>
    public static bool HasSamePropertyValues<T> ( T object1 , T object2 )
        where T : class
    {
        return HasSamePropertyValues<T>( object1 , object2 , null );
    }

    /// <summary>
    /// Compares the public properties of any 2 objects and determines if the properties of each
    /// all contain the same value.
    /// <para> 
    /// In cases where object1 and object2 are of different Types (both being derived from Type T) 
    /// we will cast both objects down to the base Type T to ensure the property comparison is only 
    /// completed on COMMON properties.
    /// (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo --
    /// both objects will be cast to Foo for comparison)
    /// </para>
    /// </summary>
    /// <typeparam name="T">Any class with public properties.</typeparam>
    /// <param name="object1">Object to compare to object2.</param>
    /// <param name="object2">Object to compare to object1.</param>
    /// <param name="ignoredProperties">A list of <see cref="PropertyInfo"/> objects
    /// to ignore when completing the comparison.</param>
    /// <returns>A boolean value indicating whether or not the properties of each object match.</returns>
    public static bool HasSamePropertyValues<T> ( T object1 , T object2 , List<PropertyInfo> ignoredProperties )
        where T : class
    {

        // If either object is null, we can't compare anything
        if ( object1 == null || object2 == null )
        {
            return false;
        }

        Type object1Type = object1.GetType();
        Type object2Type = object2.GetType();

        // In cases where object1 and object2 are of different Types (both being derived from Type T) 
        // we will cast both objects down to the base Type T to ensure the property comparison is only 
        // completed on COMMON properties.
        // (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo --
        // both objects will be cast to Foo for comparison)
        if ( object1Type != object2Type )
        {
            object1Type = typeof ( T );
            object2Type = typeof ( T );
        }

        // Remove any properties to be ignored
        List<PropertyInfo> comparisonProps =
            RemoveProperties( object1Type.GetProperties() , ignoredProperties );

        foreach ( PropertyInfo object1Prop in comparisonProps )
        {
            Type propertyType = null;
            object object1PropValue = null;
            object object2PropValue = null;

            // Rule out an attempt to check against a property which requires
            // an index, such as one accessed via this[]
            if ( object1Prop.GetIndexParameters().GetLength( 0 ) == 0 )
            {
                // Get the value of each property
                object1PropValue = object1Prop.GetValue( object1 , null );
                object2PropValue = object2Type.GetProperty( object1Prop.Name ).GetValue( object2 , null );

                // As we are comparing 2 objects of the same type we know
                // that they both have the same properties, so grab the
                // first non-null value
                if ( object1PropValue != null )
                    propertyType = object1PropValue.GetType().GetInterface( "IComparable" );

                if ( propertyType == null )
                    if ( object2PropValue != null )
                        propertyType = object2PropValue.GetType().GetInterface( "IComparable" );
            }

            // If both objects have null values or were indexed properties, don't continue
            if ( propertyType != null )
            {
                // If one property value is null and the other is not null, 
                // they aren't equal; this is done here as a native CompareTo
                // won't work with a null value as the target
                if ( object1PropValue == null || object2PropValue == null )
                {
                    return false;
                }

                // Use the native CompareTo method
                MethodInfo nativeCompare = propertyType.GetMethod( "CompareTo" );

                // Sanity Check:
                // If we don't have a native CompareTo OR both values are null, we can't compare;
                // hence, we can't confirm the values differ... just go to the next property
                if ( nativeCompare != null )
                {
                    // Return the native CompareTo result
                    bool equal = ( 0 == (int) ( nativeCompare.Invoke( object1PropValue , new object[] {object2PropValue} ) ) );

                    if ( !equal )
                    {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /// <summary>
    /// Removes any <see cref="PropertyInfo"/> object in the supplied List of 
    /// properties from the supplied Array of properties.
    /// </summary>
    /// <param name="allProperties">Array containing master list of 
    /// <see cref="PropertyInfo"/> objects.</param>
    /// <param name="propertiesToRemove">List of <see cref="PropertyInfo"/> objects to
    /// remove from the supplied array of properties.</param>
    /// <returns>A List of <see cref="PropertyInfo"/> objects.</returns>
    private static List<PropertyInfo> RemoveProperties (
        IEnumerable<PropertyInfo> allProperties , IEnumerable<PropertyInfo> propertiesToRemove )
    {
        List<PropertyInfo> innerPropertyList = new List<PropertyInfo>();

        // Add all properties to a list for easy manipulation
        foreach ( PropertyInfo prop in allProperties )
        {
            innerPropertyList.Add( prop );
        }

        // Sanity check
        if ( propertiesToRemove != null )
        {
            // Iterate through the properties to ignore and remove them from the list of 
            // all properties, if they exist
            foreach ( PropertyInfo ignoredProp in propertiesToRemove )
            {
                if ( innerPropertyList.Contains( ignoredProp ) )
                {
                    innerPropertyList.Remove( ignoredProp );
                }
            }
        }

        return innerPropertyList;
    }
}

나는이 답변을 너무 좋아하지만 클래스 사용법의 예를보고 싶었습니다. 이 작업을 제가
진행중인

7

실제 문제 : 두 세트의 차이점을 얻는 방법?

내가 찾은 가장 빠른 방법은 먼저 집합을 사전으로 변환 한 다음 diff 'em입니다. 일반적인 접근 방식은 다음과 같습니다.

static IEnumerable<T> DictionaryDiff<K, T>(Dictionary<K, T> d1, Dictionary<K, T> d2)
{
    return from x in d1 where !d2.ContainsKey(x.Key) select x.Value;
}

그런 다음 다음과 같이 할 수 있습니다.

static public IEnumerable<PropertyInfo> PropertyDiff(Type t1, Type t2)
{
    var d1 = t1.GetProperties().ToDictionary(x => x.Name);
    var d2 = t2.GetProperties().ToDictionary(x => x.Name);
    return DictionaryDiff(d1, d2);
}

5

예. 반사를 사용하십시오 . Reflection을 사용하면 다음과 같은 작업을 수행 할 수 있습니다.

//given object of some type
object myObjectFromSomewhere;
Type myObjOriginalType = myObjectFromSomewhere.GetType();
PropertyInfo[] myProps = myObjOriginalType.GetProperties();

그런 다음 결과 PropertyInfo 클래스를 사용하여 모든 방식을 비교할 수 있습니다.


4

LINQ와 Reflection을 사용하여 동일한 유형의 두 객체 비교 NB! 이것은 기본적으로 Jon Skeet의 솔루션을 다시 작성했지만 더 간결하고 현대적인 구문입니다. 또한 약간 더 효과적인 IL을 생성해야합니다.

다음과 같이 진행됩니다.

public bool ReflectiveEquals(LocalHdTicket serverTicket, LocalHdTicket localTicket)
  {
     if (serverTicket == null && localTicket == null) return true;
     if (serverTicket == null || localTicket == null) return false;

     var firstType = serverTicket.GetType();
     // Handle type mismatch anyway you please:
     if(localTicket.GetType() != firstType) throw new Exception("Trying to compare two different object types!");

     return !(from propertyInfo in firstType.GetProperties() 
              where propertyInfo.CanRead 
              let serverValue = propertyInfo.GetValue(serverTicket, null) 
              let localValue = propertyInfo.GetValue(localTicket, null) 
              where !Equals(serverValue, localValue) 
              select serverValue).Any();
  }

2
재귀가 유용할까요? 라인 교체 where !Equals(serverValue, localValue)와 함께firstType.IsValueType ? !Equals(serverValue, localValue) : !ReflectiveEquals(serverValue, localValue)
drzaus

3
더 현대적이지만 더 컴팩트하지는 않습니다. 방금 공백을 제거하고 읽기가 더 어려워졌습니다.
Eliezer Steinbock

EliezerSteinbock은 거의 그렇지 않습니다. 그는 공백을 제거하고 읽기가 더 어려워졌지만, 그가 한 일이 아닙니다. LINQ 문은 @ jon-skeet의 답변에서 foreach 문과 다르게 컴파일됩니다. 나는 이것이 도움말 사이트이기 때문에 Jon의 대답을 선호하며 그의 서식은 더 명확하지만 더 고급 답변을 위해서는 이것도 좋습니다.
Jim Yarbro

4
"더 현대적인"것이 "읽기 더 어렵다"와 같은 경우, 우리는 잘못된 방향으로 움직이고 있습니다.
bwegs


1

많은 사람들이 재귀 적 접근 방식을 언급했듯이 검색된 이름과 속성을 전달할 수있는 함수입니다.

    public static void loopAttributes(PropertyInfo prop, string targetAttribute, object tempObject)
    {
        foreach (PropertyInfo nestedProp in prop.PropertyType.GetProperties())
        {
            if(nestedProp.Name == targetAttribute)
            {
                //found the matching attribute
            }
            loopAttributes(nestedProp, targetAttribute, prop.GetValue(tempObject);
        }
    }

//in the main function
foreach (PropertyInfo prop in rootObject.GetType().GetProperties())
{
    loopAttributes(prop, targetAttribute, rootObject);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.