Ho due oggetti dello stesso tipo e voglio scorrere le proprietà pubbliche su ciascuno di essi e avvisare l'utente su quali proprietà non corrispondono.
È possibile farlo senza sapere quali proprietà contiene l'oggetto?
Ho due oggetti dello stesso tipo e voglio scorrere le proprietà pubbliche su ciascuno di essi e avvisare l'utente su quali proprietà non corrispondono.
È possibile farlo senza sapere quali proprietà contiene l'oggetto?
Risposte:
Sì, con la riflessione - presupponendo che ciascun tipo di proprietà venga implementato in modo Equalsappropriato. Un'alternativa sarebbe usare ReflectiveEqualsricorsivamente per tutti tranne alcuni tipi conosciuti, ma diventa complicato.
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;
}
Sicuro che puoi con la riflessione. Ecco il codice per catturare le proprietà di un determinato tipo.
var info = typeof(SomeType).GetProperties();
Se puoi fornire maggiori informazioni su cosa stai confrontando sulle proprietà, possiamo mettere insieme un algoritmo diff di base. Questo codice per intstance differirà sui nomi
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);
}
.Select(...)
So che questo è probabilmente eccessivo, ma ecco la mia classe ObjectComparer che uso proprio per questo scopo:
/// <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;
}
}
Il vero problema: come ottenere la differenza tra due set?
Il modo più veloce che ho trovato è di convertire prima gli insiemi in dizionari, poi di diffonderli. Ecco un approccio generico:
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;
}
Quindi puoi fare qualcosa del genere:
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);
}
Sì. Usa la riflessione . Con Reflection, puoi fare cose come:
//given object of some type
object myObjectFromSomewhere;
Type myObjOriginalType = myObjectFromSomewhere.GetType();
PropertyInfo[] myProps = myObjOriginalType.GetProperties();
E quindi puoi utilizzare le classi PropertyInfo risultanti per confrontare tutti i tipi di cose.
Confronto tra due oggetti dello stesso tipo usando LINQ e Reflection. NB! Questa è sostanzialmente una riscrittura della soluzione di Jon Skeet, ma con una sintassi più compatta e moderna. Dovrebbe anche generare IL leggermente più efficace.
Va qualcosa del genere:
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)confirstType.IsValueType ? !Equals(serverValue, localValue) : !ReflectiveEquals(serverValue, localValue)
Type.GetProperties elencherà ciascuna delle proprietà di un determinato tipo. Quindi utilizzare PropertyInfo.GetValue per verificare i valori.
Come molti hanno menzionato l'approccio ricorsivo, questa è la funzione con cui è possibile passare il nome cercato e la proprietà per iniziare a:
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);
}