简体   繁体   English

使用反射的通用比较器

[英]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. 我不想使用IComparable接口,因为有太多的类需要实现它,并且它只能在单元测试中使用,因此反射性能不是问题。

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. 现在,我想在两端遍历IEnumerable(它总是可以枚举一个类,尽管要照顾一个不存在的情况将是一个很好的选择),并使用递归比较值。 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. 我正在使用IEnumerable非通用接口。 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). 为了不更改NonEqualProperty类,我将差值的ix直接保存在prop.Name (例如, SomeCollection_1SomeCollection_0 ,如果不同的元素是0和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 通常我会为NonEqualProperty添加一个Index属性,对于我正在使用"MISSING"字符串的“ missing”元素,如果您想使用调试器进行查看就可以了,但是有更好的方法(例如,将另一个属性“ Missing”添加到NonEqualProperty ,或者执行

 public static readonly object Missing = new object();

and use it for missing values. 并将其用于缺失值。

There are other ways without using the IEnumerable interface, like a: 还有一些不使用IEnumerable接口的方法,例如:

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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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