Farklılıkları bulmak için iki nesnenin özelliklerini karşılaştır?


155

Aynı türden iki nesne var ve bunların her birinde ortak özellikleri arasında döngü ve hangi özellikleri eşleşmediği konusunda kullanıcıyı uyarmak istiyorum.

Nesnenin hangi özellikleri içerdiğini bilmeden bunu yapmak mümkün müdür?


Sanırım bu size yardımcı olacak ... Özellikler ve değerler arasında
yineleme

Bunun size yardımcı olacağını düşünüyorum. Bir nesnenin özellikleri ve değerleri üzerinden yineleme .
Damien Doumer

Yanıtlar:


212

Evet, yansıma ile - her özellik türünün Equalsuygun şekilde uygulandığını varsayarsak Bir alternatif, ReflectiveEqualsbilinen bazı türler hariç herkes için özyinelemeli olarak kullanmak olabilir , ancak bu zorlaşır.

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

Özyinelemeyi bu yöntemle kullanmak ve nesnenin sahip olabileceği tüm koleksiyonları karşılaştırmak mümkün müdür? örneğin Object1 -> Liste (Okul) -> Liste (Sınıflar) -> Liste (Öğrenci)
Peter PitLock

@PeterPitLock: Koleksiyonlar için muhtemelen farklı işlemler istersiniz - sadece listelerdeki özellikleri karşılaştırmak işe yaramaz.
Jon Skeet

2
Teşekkürler jon, MasterObject (MO) ve MasterObject'in sadece soyulmuş bir versiyonu olan bir LightweightMasterObject (LWMO) var - ama her ikisi de koleksiyonları var - Im özyineleme ile sağlanan kodu kullanıp kullanamayacağımı görmeye çalışıyorum - LWMO boş başlatıldığında, ancak MO ve özelliklerindeki her bir koleksiyondan geçerken - karşılık gelen LWMO'nun değeri ayarlanır - bu uygulama sağlanan kodda özyinelemeye izin verir mi?
Peter PitLock

@PeterPitLock: Bu noktada yeni bir soru sormanız gerektiği anlaşılıyor, temel olarak - bunun cevapladığı soru gereksinimlerinize yeterince yakın değil.
Jon Skeet

42

Tabii ki yansıması ile yapabilirsiniz. İşte belirli bir türün özelliklerini kapmak için kod.

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

Özellikler hakkında ne karşılaştırdığınız hakkında daha fazla bilgi verebilirseniz, temel bir fark algoritmasını bir araya getirebiliriz. Bu intstance kodu adlara göre değişir

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

Değerlerin eşleşmediği yerde aynı türden iki nesne anlamına geldiğini düşünüyorum .
BFree

@JaredPar: Diffing Çalışmıyor. PropertyInfo nesneleri, türün kendisi olmadıkça kesinlikle aynı değildir ...
Mehrdad Afshari

@Mehrdad, benimki isimler için sadece temel bir örnekti. Ben daha özel yapmadan önce aradıklarına netlik vermek için OP bekliyordum.
JaredPar

1
@JaredPar: Anlıyorum, ama bu gerçekten isimler için işe yaramıyor. Bu fikri iletebilse de, biraz yanıltıcıdır. Dizi yine de eşit olmayacak. Ben eklemeyi öneririm.Select(...)
Mehrdad Afshari

üzgünüm, sadece açıklığa kavuşturmak için özelliklerin değerlerinin nerede farklı olduğunu kastetmiştim. Teşekkürler
Gavin

7

Bunun muhtemelen aşırı olduğunu biliyorum, ama işte bu amaçla kullandığım ObjectComparer sınıfım:

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

Bu yanıtı çok seviyorum ama sınıfların örnek kullanımını gördüm. Kesinlikle üzerinde çalıştığım bir proje için
kullanacağım

7

Asıl sorun: İki setin farkı nasıl elde edilir?

Bulduğum en hızlı yol, önce setleri sözlüklere dönüştürmek, sonra fark etmektir. İşte genel bir yaklaşım:

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

Sonra böyle bir şey yapabilirsiniz:

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

Evet. Yansıma kullanın . Yansıma ile aşağıdakileri yapabilirsiniz:

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

Sonra her şeyi karşılaştırmak için elde edilen PropertyInfo sınıflarını kullanabilirsiniz.


4

LINQ ve Reflection kullanarak aynı türdeki iki nesneyi karşılaştırma. NB! Bu temelde Jon Skeet'in çözümünün yeniden yazılmasıdır, ancak daha kompakt ve modern bir sözdizimiyle. Ayrıca biraz daha etkili IL üretmelidir.

Böyle bir şey gider:

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
özyineleme faydalı olur mu? çizgi yerine where !Equals(serverValue, localValue)birliktefirstType.IsValueType ? !Equals(serverValue, localValue) : !ReflectiveEquals(serverValue, localValue)
drzaus

3
Daha modern olabilir, ancak daha kompakt olmayabilir. Bir sürü boşluktan kurtuldunuz ve okumayı zorlaştırdınız.
Eliezer Steinbock

EliezerSteinbock neredeyse böyle değil. Beyaz alandan kurtulurken ve okumayı zorlaştırırken, yaptığı şey SADECE değildi. Buradaki LINQ ifadesi @ jon-skeet'in cevabındaki foreach ifadesinden farklı derler. Jon'un cevabını tercih ediyorum çünkü bu bir yardım sitesi ve biçimlendirmesi daha açık, ancak daha gelişmiş bir cevap için bu da iyi.
Jim Yarbro

4
Eğer "daha modern" "okumak daha zor" ise, yanlış yönde ilerliyoruz.
bwegs


1

Birçoğunun özyinelemeli yaklaşımdan bahsettiği gibi, bu, aranan adı ve başlamak için özelliği aktarabileceğiniz işlevdir:

    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);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.