简体   繁体   中英

Generic comparer using reflection

I am putting together simple generic comparer for unit testing.
I don't want to use IComparable interface because there is just too much classes that would need to implement it, and it will be only used in Unit tests so performance of reflection is not an issue.

So far I have this:

 public IEnumerable<NonEqualProperty> Compare<T>(T first, T second) where T : class
        {
            var list = new List<NonEqualProperty>();
            var type = first.GetType();
            var properties = type.GetProperties();

            var basicTypes = properties.Where(p => !p.PropertyType.IsClass && !p.PropertyType.IsInterface
                                                   || p.PropertyType == typeof(string));


            foreach (var prop in basicTypes)
            {
                var value1 = prop.GetValue(first, null);
                var value2 = prop.GetValue(second, null);

                if (value1 != null && value2 != null && value1.Equals(value2) || value1 == null && value2 == null )
                    continue;

                list.Add(new NonEqualProperty(prop.Name, value1, value2));
            }

            var enumerableTypes =
                from prop in properties
                from interfaceType in prop.PropertyType.GetInterfaces()
                where interfaceType.IsGenericType
                let baseInterface = interfaceType.GetGenericTypeDefinition()
                where prop.PropertyType != typeof(string) && baseInterface == typeof(IEnumerable<>) || baseInterface == typeof(IEnumerable)
                select prop;

            foreach (var prop in enumerableTypes)
            {
                var collection = prop.GetValue(first, null);
            }


            return list;
        }

So comparison of all simple types + string works.

Now I would like to iterate through IEnumerable (it's always enumerable of a class, though it would be great to take care of the case when it's not) on both sides and compare values using recursion. Something like this:

foreach (var prop in enumerableTypes)
{
    var typeOfItemsInList= ...;
    var collection1 = (IEnumerable<typeOfItemsInList>) prop.GetValue(first, null);
    var collection2 = (IEnumerable<typeOfItemsInList>) prop.GetValue(second, null);

    for (int i = 0; i < collection1.Count; i++)
    {
        var item1 = collection1[i];
        var item2 = collection2[i];

        Compare<typeOfItemsInList>(item1, item2, list);
    }

}

How would I achieve that?

Unequal count, or order of items in lists are not taken into account here - I would fix it later.

Something similar to this:

public static IEnumerable<NonEqualProperty> Compare<T>(T first, T second) where T : class {
    var list = new List<NonEqualProperty>();
    var type = first.GetType();
    var properties = type.GetProperties();

    var basicTypes = properties.Where(p => !p.PropertyType.IsClass && !p.PropertyType.IsInterface
                                            || p.PropertyType == typeof(string));

    foreach (var prop in basicTypes) {
        var value1 = prop.GetValue(first, null);
        var value2 = prop.GetValue(second, null);

        if (object.Equals(value1, value2))
            continue;

        list.Add(new NonEqualProperty(prop.Name, value1, value2));
    }

    var enumerableTypes =
        from prop in properties
        where prop.PropertyType == typeof(IEnumerable) ||
            prop.PropertyType.GetInterfaces().Any(x => x == typeof(IEnumerable))
        select prop;

    foreach (var prop in enumerableTypes) {
        var value1 = (IEnumerable)prop.GetValue(first, null);
        var value2 = (IEnumerable)prop.GetValue(second, null);

        if (object.Equals(value1, value2))
            continue;

        if (value1 == null || value2 == null) {
            list.Add(new NonEqualProperty(prop.Name, value1, value2));
            continue;
        }

        IEnumerator enu1 = null, enu2 = null;

        try {
            try {
                enu1 = value1.GetEnumerator();
                enu2 = value2.GetEnumerator();

                int ix = -1;

                while (true) {
                    bool next1 = enu1.MoveNext();
                    bool next2 = enu2.MoveNext();
                    ix++;

                    if (!next1) {
                        while (next2) {
                            list.Add(new NonEqualProperty(prop.Name + "_" + ix, "MISSING", enu2.Current));
                            ix++;
                            next2 = enu2.MoveNext();
                        }

                        break;
                    }

                    if (!next2) {
                        while (next1) {
                            list.Add(new NonEqualProperty(prop.Name + "_" + ix, enu1.Current, "MISSING"));
                            ix++;
                            next1 = enu1.MoveNext();
                        }

                        break;
                    }

                    if (enu1.Current != null) {
                        var type1 = enu1.Current.GetType();

                        if ((type1.IsClass || type1.IsInterface) && type1 != typeof(string)) {
                            continue;
                        }
                    }

                    if (enu2.Current != null) {
                        var type2 = enu2.Current.GetType();

                        if ((type2.IsClass || type2.IsInterface) && type2 != typeof(string)) {
                            continue;
                        }
                    }

                    if (!object.Equals(enu1.Current, enu2.Current)) {
                        list.Add(new NonEqualProperty(prop.Name + "_" + ix, enu1.Current, enu2.Current));
                    }
                }
            } finally {
                var disp2 = enu2 as IDisposable;

                if (disp2 != null) {
                    disp2.Dispose();
                }
            }
        } finally {
            var disp1 = enu1 as IDisposable;

            if (disp1 != null) {
                disp1.Dispose();
            }
        }
    }

    return list;
}

I'm using the IEnumerable non-generic interface. Note that I'm doing the type-test on each element.

To not change the NonEqualProperty class I'm saving the ix of the difference directly in the prop.Name (so like SomeCollection_0 , SomeCollection_1 if the different elements are 0 and 1). Normally I would add an Index property to NonEqualProperty For the "missing" elements I'm using a "MISSING" string, that is ok if you want to take a look at it with the debugger, but there are better ways to do it (add another property "Missing" to NonEqualProperty for example, or do a

 public static readonly object Missing = new object();

and use it for missing values.

There are other ways without using the IEnumerable interface, like a:

private static IEnumerable<NonEqualProperty> CompareCollection<T>(IEnumerable<T> firstColl, IEnumerable<T> secondColl) {
    var list = new List<NonEqualProperty>();

    // Do the comparison

    return list;
}

private static MethodInfo CompareCollectionMethod = typeof(Program).GetMethod("CompareCollection", BindingFlags.Static | BindingFlags.NonPublic);

and then

    var value1 = (IEnumerable)prop.GetValue(first, null);
    var value2 = (IEnumerable)prop.GetValue(second, null);

    if (object.Equals(value1, value2))
        continue;

    if (prop.PropertyType != typeof(IEnumerable)) {
        var ienumt = prop.PropertyType.GetInterfaces().Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>)).FirstOrDefault();

        if (ienumt != null) {
            var t = ienumt.GetGenericArguments(); // T of IEnumerable<T>

            if ((t[0].IsClass || t[0].IsInterface) && t[0] != typeof(string)) {
                continue;
            }

            var method = CompareCollectionMethod.MakeGenericMethod(t);
            var result = (IEnumerable<NonEqualProperty>)method.Invoke(null, new[] { value1, value2 });
            list.AddRange(result);
            continue;
        }
    }

    if (value1 == null || value2 == null) {
        list.Add(new NonEqualProperty(prop.Name, value1, value2));
        continue;
    }

    // continue with the code for non-generic IEnumerable

    IEnumerator enu1 = null, enu2 = null;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM