通过C#中的对象属性循环

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

是否有可能做到这一点,而不知道对象包含什么属性?

谢谢

是的,用reflection – 假定每个属性types都适当地实现Equals 。 另一种方法是对所有已知types的所有typesrecursion地使用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; } 

当然你可以反思。 这里是获取给定types属性的代码。

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

如果你可以提供更多关于你正在比较的属性的信息,我们可以把基本的差异algorithm放在一起。 这个intstance的代码将不同于名称

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

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

我发现的最快的方法是先将这些集合转换成字典,然后再比较它们。 这是一个通用的方法:

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

使用LINQ和Reflection比较两个相同types的对象。 NB! 这基本上是Jon Skeet的解决scheme的重写,但是具有更紧凑和更现代的语法。 它也应该产生稍微更有效的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(); } 

是。 使用reflection 。 有了Reflection,你可以做如下的事情:

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

然后你可以使用最终的PropertyInfo类来比较所有的事情。

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