• .NET中如何深度判断2个对象相等


    背景

    最近在群里,有人问如何深度比较2个对象相等,感觉很有意思,就自己研究了一下,并写了一个开源的小类库,地址如下https://github.com/lamondlu/ObjectEquality。

    如果想直接使用这个类库,可以使用Nuget进行安装

    Install-Package ObjectEquality
    

    对象比较有几种情况

    1. 对象是值类型或者String,这里仅需要判断值是否相等
    2. 对象是Struct,需要判断Struct的每个字段是否一致
    3. 对象是集合,需要判断对应位置的对象是否相等
    4. 对象是数组,需要判断对应位置的对象是否相等
    5. 对象是Class, 需要判断Class的每个字段是否一致

    这里可能有缺漏,大家可以帮我补充。

    编写代码

    这里我首先创建了一个IEquality接口,在其中定义了一个IsEqual方法,这个方法就是判断2个对象是否一致的方法。后面我会针对上面说明的几种对比场景,分别创建对应的实现类。

        public interface IEquality
        {
            Func<object, bool> MatchCondition { get; }
            bool IsEqual(object source, object target);
        }
    

    这里MatchCondition是一个委托,它定义了当前对比类的匹配条件。

    第二步,我们针对上述的几种对比场景,创建对应的实现类

    值类型相等判断实现类

        internal class ValueTypeEquality : IEquality
        {
            public Func<object, bool> MatchCondition
            {
                get
                {
                    return p => p.GetType().IsValueType || p.GetType() == typeof(string);
                }
            }
    
            public bool IsEqual(object source, object target)
            {
                return source.Equals(target);
            }
        }
    

    值类型的判断比较简单,直接调用Object类的Equals方法即可。

    String类型虽然不是值类型,但是这里我们需要把它归到值类型中。

    Struct相等判断实现类

        internal class StructEquality : IEquality
        {
            public Func<object, bool> MatchCondition
            {
                get
                {
                    return p => p.GetType().IsValueType 
                        && !p.GetType().IsPrimitive 
                        && !p.GetType().IsEnum;
                }
            }
    
            public bool IsEqual(object source, object target)
            {
                var type = source.GetType();
    
                foreach (var prop in type.GetProperties())
                {
                    var equality = EqualityCollection.Equalities
                        .First(p => p.MatchCondition(prop.GetValue(source)));
    
                    var result = equality.IsEqual(prop.GetValue(source),
                        prop.GetValue(target));
    
                    if (!result)
                    {
                        return false;
                    }
                }
    
                return true;
            }
        }	
    

    这里我们读取了Struct中的每个属性,分别进行判断,如果有一个判断失败,即认为2个Struct对象不相等。

    这里EqualityCollection是判断器集合,后续会添加这个类的代码。

    集合相等判断实现类

    	internal class GenericCollectionEquality : IEquality
        {
            public Func<object, bool> MatchCondition
            {
                get
                {
                    return p => p.GetType().IsGenericType;
                }
            }
    
            public bool IsEqual(object source, object target)
            {
                var type = source.GetType();
                var genericType = type.GetGenericArguments()[0];
    
                var genericCollectionType = typeof(IEnumerable<>).MakeGenericType(genericType);
    
                if (type.GetInterfaces().Any(p => p == genericCollectionType))
                {
                    var countMethod = type.GetMethod("get_Count");
    
                    var sourceCount = (int)countMethod.Invoke(source, null);
                    var targetCount = (int)countMethod.Invoke(target, null);
    
                    if (sourceCount != targetCount)
                    {
                        return false;
                    }
    
                    var sourceCollection = (source as IEnumerable<object>).ToList();
                    var targetCollection = (target as IEnumerable<object>).ToList();
    
                    for (var i = 0; i < sourceCount; i++)
                    {
                        var equality = EqualityCollection.Equalities.First(p => p.MatchCondition(sourceCollection[i]));
    
                        var result = equality.IsEqual(sourceCollection[i], targetCollection[i]);
    
                        if (!result)
                        {
                            return false;
                        }
                    }
                }
    
                return true;
            }
        }
    

    这里我们首先判断了集合的元素的数量是否一致,如果不一致,即这2个集合不相等。如果一致,我们继续判断对应位置的每个元素是否一致,如果全部都一直,则2个集合相当,否则2个集合不相等。

    数组相等判断实现类

    	internal class ArrayEquality : IEquality
        {
            public Func<object, bool> MatchCondition
            {
                get
                {
                    return p => p.GetType().IsArray;
                }
            }
    
            public bool IsEqual(object source, object target)
            {
                Array s = source as Array;
                Array t = target as Array;
    
                if (s.Length != t.Length)
                {
                    return false;
                }
    
                
                for (var i = 0; i < s.Length; i++)
                {
                    var equality = EqualityCollection.Equalities
                    	.First(p => p.MatchCondition(s.GetValue(i)));
    
                    var result = equality.IsEqual(s.GetValue(i), t.GetValue(i));
    
                    if (!result)
                    {
                        return false;
                    }
                }
    
                return true;
            }
        }
    

    数组相等的判断类似集合,我们首先判断数组的长度是否一致,然后判断对应位置的元素是否一致。

    类判断相等实现类

    	internal class ClassEquality : IEquality
        {
            public Func<object, bool> MatchCondition
            {
                get
                {
                    return p => p.GetType().IsClass;
                }
            }
    
            public bool IsEqual(object source, object target)
            {
                var type = source.GetType();
    
                foreach (var prop in type.GetProperties())
                {
                    var equality = EqualityCollection.Equalities
                    	.First(p => p.MatchCondition(prop.GetValue(source)));
    
                    var result = equality.IsEqual(prop.GetValue(source), prop.GetValue(target));
    
                    if (!result)
                    {
                        return false;
                    }
                }
    
                return true;
            }
        }
    

    添加判断相等实现类集合

    	public static class EqualityCollection
        {
            public static readonly List<IEquality> Equalities = new List<IEquality> {
                new StructEquality(),
                new ValueTypeEquality(),
                new ArrayEquality(),
                new GenericCollectionEquality(),
                new ClassEquality()
            };
        }
    

    这里我们定义了一个静态类,来存储程序中使用的所有判断器。

    这里在判断器集合中,实现类的其实是有顺序的,StructEquality必须要放到ValueTypeEquality的前面,因为Struct也是值类型,如果不放到最前面,会导致判断失败。

    创建判断器入口类

    	public class ObjectEquality
        {
            public bool IsEqual(object source, object target)
            {
                if (source.GetType() != target.GetType())
                {
                    return false;
                }
    
                if (source == null && target == null)
                {
                    return true;
                }
                else if (source == null && target != null)
                {
                    return false;
                }
                else if (source != null && target == null)
                {
                    return false;
                }
    
                var equality = EqualityCollection.Equalities
                	.First(p => p.MatchCondition(source));
    
                return equality.IsEqual(source, target);
            }
        }
    

    前面所有实现类的访问级别都是Internal, 所以我们需要创建一个判断器入口类, 外部只能通过ObjectEquality类的实例来实现判断相等。

    最终效果

    下面我列举几个测试用例,看看效果是不是我们想要的

    对比Struct

    	public struct DemoStruct
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }
    
        var a = new DemoStruct();
        a.Id = 1;
        a.Name = "Test";
    
        var b = new DemoStruct();
        b.Id = 1;
        b.Name = "Test";
        
        var c = new DemoStruct();
        b.Id = 2;
        b.Name = "Test";
        
        ObjectEquality objectEquality = new ObjectEquality();
        objectEquality.IsEqual(a,b); //true
        objectEquality.IsEqual(a,c); //false
    

    对比类

    	public class SimpleClass
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }
    
        var a = new SimpleClass
        {
            Id = 1,
            Name = "A"
        };
    
        var b = new SimpleClass
        {
            Id = 1,
            Name = "A"
        };
        
        var c = new SimpleClass
        {
            Id = 2,
            Name = "A"
        };
        
        ObjectEquality objectEquality = new ObjectEquality();
        objectEquality.IsEqual(a,b); //true
        objectEquality.IsEqual(a,c); //false
    

    对比数组

     	var a = new int[] { 1, 2, 3 };
     	var b = new int[] { 1, 2, 3 };
     	var c = new int[] { 1, 1, 2 }; 
     	
     	ObjectEquality objectEquality = new ObjectEquality();
        objectEquality.IsEqual(a,b); //true
        objectEquality.IsEqual(a,c); //false
    
  • 相关阅读:
    Codeforces Round #719 (Div. 3) 题解
    Codeforces Global Round 14 A~F题解
    AtCoder Beginner Contest 199 题解
    Codeforces Round #716 (Div. 2) A~D 题解
    Codeforces Round #713 (Div. 3) 题解
    Codeforces Round #712 (Div. 2) A~E 题解
    CodeCraft-21 and Codeforces Round #711 (Div. 2) A~E 题解
    CF839 D 莫比乌斯反演
    java存大数和高精度浮点数(BigInteger与BigDecimal)
    java科学计算常用方法(Math)
  • 原文地址:https://www.cnblogs.com/lwqlun/p/10159770.html
Copyright © 2020-2023  润新知