"Deep compare" dictionaries

747 views Asked by At

I'm doing "deep comparison" of data structures for audit logging (who changed what members, when). I'm using reflection for this, recursing into the structures and comparing them. I've hit a problem though with those containing dictionaries.

I can detect that a member is a dictionary by typeof(IDictionary).IsAssignableFrom(memberType). My plan is then to collect the keys which are present in both objects, and continue recursion on those. However, IDictionary.Keys is an ICollection, which is not extended by LINQ. Not knowing the type of the keys, how could I achieve this?

Maybe this method is suboptimal (I'm not that experienced in the Generics/Reflection combo), should I do this in another way?

2

There are 2 answers

0
carlpett On BEST ANSWER

Found a solution myself. Here ChangedProperties is a type containing property name, and values pre/post change.

if (typeof(IDictionary).IsAssignableFrom(propertyType))
{
    Type keyType = propertyType.GetGenericArguments()[0],
         valueType = propertyType.GetGenericArguments()[1];
    Type hashsetType = typeof(HashSet<>).MakeGenericType(keyType);
    var hashsetCtor = hashsetType.GetConstructor(new[] { typeof(IEnumerable<>).MakeGenericType(keyType) });

    dynamic aDict = a;
    dynamic bDict = b;
    dynamic aKeys = hashsetCtor.Invoke(new object[] { aDict.Keys });
    dynamic bKeys = hashsetCtor.Invoke(new object[] { bDict.Keys });

    List<ChangedProperty> changes = new List<ChangedProperty>();
    foreach (var key in Enumerable.Intersect(aKeys, bKeys))
    {
            // Present in both dictionaries. Recurse further
    }
    foreach (var key in Enumerable.Except(aKeys, bKeys))
    {
          // Key was removed
    }
    foreach (var key in Enumerable.Except(bKeys, aKeys))
    {
          // Key was added
    }

    return changes;
}
0
Tomislav Markovski On

This will help you with your reflection iterations.

IDictionary<int, string> t;

bool t.GetType().IsGenericType
Type[] t.GetType().GetGenericArguments() 
// you can do foreach here and see again if type is generic

You can create a helper method where you first test if type is generic, and then check the generic argument types. This will test not only for generic dictionaries, but any type that has generic arguments. IList, KeyValuePair etc.

public static bool IsType(Type inputType, Type targetType)
{
    if (inputType.IsGenericType)
    {
        Type[] genericArgs = inputType.GetGenericArguments();
        var foundType = false;
        foreach (var item in genericArgs)
        {
            if (IsType(item, targetType))
                foundType = true;
        }
        return foundType;
    }
    return inputType.IsAssignableFrom(targetType);
}