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

发布时间:2018-12-22 12:42  浏览次数:230



背景


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

如果想直接使用这个类库,可以使用Nuget进行安装
Install-Package ObjectEquality
对象比较有几种情况

* 对象是值类型或者String,这里仅需要判断值是否相等
* 对象是Struct,需要判断Struct的每个字段是否一致
* 对象是集合,需要判断对应位置的对象是否相等
* 对象是数组,需要判断对应位置的对象是否相等
* 对象是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

其他工具相关

标签

归档

阅读排行