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

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

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



답변

그렇습니다 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;
}


답변

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

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


답변

나는 이것이 과잉 일 것임을 알고 있지만,이 목적을 위해 사용하는 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;
    }
}


답변

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

내가 찾은 가장 빠른 방법은 먼저 집합을 사전으로 변환 한 다음 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);
}


답변

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

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

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


답변

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


답변

Type.GetProperties 는 주어진 유형의 각 속성을 나열합니다. 그런 다음 PropertyInfo.GetValue 를 사용 하여 값을 확인하십시오.