【问题标题】:Compare two objects' properties to find differences?比较两个对象的属性以发现差异?
【发布时间】:2010-10-31 18:18:21
【问题描述】:

我有两个相同类型的对象,我想遍历每个对象的公共属性并提醒用户哪些属性不匹配。

是否可以在不知道对象包含哪些属性的情况下执行此操作?

【问题讨论】:

标签: c# .net


【解决方案1】:

Compare NET Objects可以帮到你!

CompareLogic logic = new CompareLogic();
var compare = logic.Compare(obj1, obj2);
comparacao.Differences.ForEach(diff => Debug.Write(diff.PropertyName));
// Or formatted summary
Debug.Write(comparacao.DifferencesString);

【讨论】:

    【解决方案2】:

    正如许多人提到的递归方法,这是您可以将搜索到的名称和属性传递给的函数:

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

    【讨论】:

      【解决方案3】:

      使用 LINQ 和反射比较两个相同类型的对象。 注意!这基本上是对 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();
        }
      

      【讨论】:

      • 递归有用吗?将where !Equals(serverValue, localValue) 行替换为firstType.IsValueType ? !Equals(serverValue, localValue) : !ReflectiveEquals(serverValue, localValue)
      • 可能更现代,但不是更紧凑。你只是去掉了一大堆空白,使它更难阅读。
      • EliezerSteinbock 并非如此。虽然他确实摆脱了空白并且确实使阅读变得更加困难,但这不仅仅是他所做的。那里的 LINQ 语句的编译方式与 @jon-skeet 答案中的 foreach 语句不同。我更喜欢 Jon 的答案,因为这是一个帮助网站,而且他的格式更清晰,但对于更高级的答案,这个也不错。
      • 如果“更现代”等同于“更难阅读”,那么我们就走错了方向。
      【解决方案4】:

      真正的问题:如何获得两组的差异?

      我发现最快的方法是先将集合转换为字典,然后再进行 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】:

        当然你可以用反射。这是从给定类型中获取属性的代码。

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

        如果您能提供更多关于您所比较的属性的信息,我们可以汇总一个基本的差异算法。此实例代码将在名称上有所不同

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

        【讨论】:

        • 我认为他的意思是两个相同类型的对象,其中 不匹配。
        • @JaredPar:区分不起作用。 PropertyInfo 对象肯定不相同,除非类型本身是...
        • @Mehrdad,我的只是名称的一个基本示例。在我更具体之前,我一直在等待 OP 明确他们在寻找什么。
        • @JaredPar:我明白,但这对名称并不适用。虽然它可能会传达这个想法,但它有点误导。无论如何,顺序不会相等。我建议添加.Select(...)
        • 对不起,只是为了澄清我的意思是属性中的值不同的地方。谢谢
        【解决方案6】:

        是的,使用反射 - 假设每个属性类型都适当地实现了 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 -> List(of School) -> List(of Classes) -> List(of Students)
        • @PeterPitLock:您可能希望对集合进行不同的处理 - 仅比较列表上的属性是行不通的。
        • 谢谢乔恩,我有一个 MasterObject (MO) 和一个 LightweightMasterObject (LWMO),它们只是 MasterObject 的精简版 - 但两者都有集合 - 我想看看我是否可以使用代码提供递归 - LWMO 在启动时为空,但当遍历 MO 及其属性的每个集合时 - 设置了相应的 LWMO 的值 - 此实现是否允许对提供的代码进行递归?
        • @PeterPitLock:听起来你现在应该问一个新问题,基本上——这个回答的问题与你的要求不够接近。
        【解决方案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;
            }
        }
        

        【讨论】:

        • 我非常喜欢这个答案,但我希望看到类的示例用法。我肯定会在我正在从事的项目中使用它
        【解决方案8】:

        Type.GetProperties 将列出给定类型的每个属性。然后使用PropertyInfo.GetValue 检查值。

        【讨论】:

          【解决方案9】:

          是的。使用Reflection。使用反射,您可以执行以下操作:

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

          然后您可以使用生成的 PropertyInfo 类来比较各种事物。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2014-04-02
            • 1970-01-01
            • 2020-01-25
            • 1970-01-01
            • 1970-01-01
            • 2011-02-02
            • 1970-01-01
            • 2023-03-31
            相关资源
            最近更新 更多