快速创建 IEqualityComparer 实例:改进
2013-12-31 23:59
381 查看
两年前,我写了篇文章《快速创建 IEqualityComparer<T> 和 IComparer<T> 的实例》,文中给出了一个用于快速创建 IEqualityComparer<T> 实例的类 Equality<T>。
在后来的使用中发现了一些不足,在此进行一些改进,以便更好的使用。原文中的 Equality<T> 实现如下:
代码中的问题使用红色粗体标出。
在改进之前,我们需要先弄清两个关于 null 值的两个问题:
请告诉我 b1 和 b2 的值。
请告诉我 h1 和 h2 的值。
建议大家想下这两个问题,答案就不给出了,自行调试吧。
你的答案和调试得出的结果可能会有出入,如果这样你得好好思考下了。
以上代码黄色高亮部分为新加入代码。
用法:
也许有的朋友认为应该将这行代码修改为:
这样得出 b4 的值为 true.
我不赞同这种方式,我的观点是:“p=>p.Name”指定使用 Person 的 Name 进行相等比较,Person若不存在(值为 null), Name 更不存在,也谈不上相等,所以应返回 false。
当然还有另一种想法,Person 不存在,没法比,应该抛出异常。
RuntimeHelpers 类在 System.Runtime.CompilerServices 命名空间下,我在反编译 Object 时,在 GetHashCode() 方法中发现了它。
要创建 Employee 的相等比较器,根据其学校(School)的所在城市(City)。不考虑一个 Employee 多个 School 的情况,但要考虑 Employee 的 School 属性为 null 的情况(可能没上过学)。
用以下方式创建:
运行时,可能会出错。执行比较时,遇到 Employee 的 School 属性为 null ,便会抛出 NullReferenceException。
一种可行的写法是:
是的,分两步。也许是麻烦了些,不过试想下如果没有 Equality<T> 类的帮助,如果实现这个这个相等比较器?相当麻烦,不信可以试着写下。
简单测试下:
还是要求创建 Employee 的相等比较器,根据 Employee 的 School 的 City 的 Country 来判断。要考虑各引用属性的为 null 时的情形。
还不过瘾,就再加点难度! Country 比较时不考虑大小写。
嘻嘻,有谁能告诉我如何创建,可以使用本文中的 Equality<T>,也可以不用。当然,越简洁越好。
知道的话,请回复我,非常期待你的参与!
祝大家 2014 年新年快乐!有更大的收获!
在后来的使用中发现了一些不足,在此进行一些改进,以便更好的使用。原文中的 Equality<T> 实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | public static class Equality<T> { public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector) { return new CommonEqualityComparer<V>(keySelector); } public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector, IEqualityComparer<V> comparer) { return new CommonEqualityComparer<V>(keySelector, comparer); } class CommonEqualityComparer<V> : IEqualityComparer<T> { private Func<T, V> keySelector; private IEqualityComparer<V> comparer; public CommonEqualityComparer(Func<T, V> keySelector, IEqualityComparer<V> comparer) { this.keySelector = keySelector; this.comparer = comparer; } public CommonEqualityComparer(Func<T, V> keySelector) : this(keySelector, EqualityComparer<V>.Default) { } public bool Equals(T x, T y) { // 此处未处理参数 x 和 y 为空的情况 return comparer.Equals(keySelector(x), keySelector(y)); } public int GetHashCode(T obj) { // 此处未处理参数 obj 为空的情况 return comparer.GetHashCode(keySelector(obj)); } } } |
在改进之前,我们需要先弄清两个关于 null 值的两个问题:
关于 null 的两个问题
将定有一个 Person 类:1 2 3 4 | public class Peron { public string Name { get; set; } } |
问题一,两个 null 值是否相等?
1 2 3 4 | Peron p1 = new Peron { Name = null }; Peron p2 = new Peron { Name = null }; Peron p3 = null; Peron p4 = null; bool b1 = p1.Name == p2.Name; bool b2 = p3 == p4; |
问题二,为 null 时 HashCode 应该是什么?
1 2 | var h1 = StringComparer.InvariantCulture.GetHashCode(p1.Name); var h2 = EqualityComparer<Peron>.Default.GetHashCode(p3); |
建议大家想下这两个问题,答案就不给出了,自行调试吧。
你的答案和调试得出的结果可能会有出入,如果这样你得好好思考下了。
Equality<T> 改进后的代码
改进后,代码如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | public static class Equality<T> { public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector) { return new CommonEqualityComparer<V>(keySelector); } public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector, IEqualityComparer<V> comparer) { return new CommonEqualityComparer<V>(keySelector, comparer); } class CommonEqualityComparer<V> : IEqualityComparer<T> { private Func<T, V> keySelector; private IEqualityComparer<V> comparer; public CommonEqualityComparer(Func<T, V> keySelector, IEqualityComparer<V> comparer) { this.keySelector = keySelector; this.comparer = comparer; } public CommonEqualityComparer(Func<T, V> keySelector) : this(keySelector, EqualityComparer<V>.Default) { } public bool Equals(T x, T y) { if (x == null || y == null) return false; return comparer.Equals(keySelector(x), keySelector(y)); } public int GetHashCode(T obj) { if (obj == null) return 0; return comparer.GetHashCode(keySelector(obj)); } } } |
用法:
1 2 3 4 | var personNameComparer = Equality<Peron>.CreateComparer(p => p.Name); // Peron p5 = new Peron { Name = "Bob" }; Peron p6 = new Peron { Name = "Tom" }; var b3 = personNameComparer.Equals(p5, p6); // false // Peron p7 = null; Peron p8 = null; var b4 = personNameComparer.Equals(p7, p8); // false |
第 28 行代码
此行代码会有很大争议,它会影响 p7 与 p8 比较的结果 b4。也许有的朋友认为应该将这行代码修改为:
1 2 3 4 | if(x== null) { if (y == null) return true; else return false; } else { if (y == null) return false; else return comparer.Equals(keySelector(x), keySelector(y)); } |
我不赞同这种方式,我的观点是:“p=>p.Name”指定使用 Person 的 Name 进行相等比较,Person若不存在(值为 null), Name 更不存在,也谈不上相等,所以应返回 false。
当然还有另一种想法,Person 不存在,没法比,应该抛出异常。
第 33 行代码
也可以写成:1 | return RuntimeHelpers.GetHashCode(null); |
复杂情况下的使用
一位园友问我这样一个问题,如下两个类:1 2 3 4 | public class Employee { public School School { get; set; } } public class School { public string City { get; set; } } |
用以下方式创建:
1 | var employeeComparer = Equality<Employee>.CreateComparer(i => i.School.City); |
一种可行的写法是:
1 2 | var companylComparer = Equality<School>.CreateComparer(i => i.City); var employeeComparer = Equality<Employee>.CreateComparer(i => i.School, companylComparer); |
简单测试下:
1 2 3 4 | var v0 = new Employee { School = new School { City = "Beijing" } }; var v1 = new Employee { School = new School { City = "Beijing" } }; var v2 = new Employee { School = new School { City = "Shanghai" } }; var v3 = new Employee { School = null }; var v4 = new Employee { School = null }; var b1 = employeeComparer.Equals(v0, v1); // true var b2 = employeeComparer.Equals(v0, v2); // false var b3 = employeeComparer.Equals(v0, v3); // false var b4 = employeeComparer.Equals(v3, v4); // false |
再搞复杂一点
把前面的 City 变成一个类,代码如下:1 2 3 4 | public class Employee { public School School { get; set; } } public class School { public City City { get; set; } } public class City { public string Name { get; set; } public string Country { get; set; } } |
还不过瘾,就再加点难度! Country 比较时不考虑大小写。
嘻嘻,有谁能告诉我如何创建,可以使用本文中的 Equality<T>,也可以不用。当然,越简洁越好。
知道的话,请回复我,非常期待你的参与!
后记
终于赶在最后一分钟完成了2013年最后一篇文章。三更半夜,行文仓促,如有疏漏,请多包涵。祝大家 2014 年新年快乐!有更大的收获!
相关文章推荐
- 快速创建 IEqualityComparer<T> 和 IComparer<T> 的实例
- 快速创建 IEqualityComparer<T> 和 IComparer<T> 的实例
- 于快速创建 IEqualityComparer<T> 实例的类 Equality<T>
- 快速创建 IEqualityComparer<T> 和 IComparer<T> 的实例
- 一分钟在云端快速创建MySQL数据库实例
- YII Framework框架使用YIIC快速创建YII应用之migrate用法实例详解
- 977dh用vbs通过wsh创建桌面快捷方式,快速启动,修改ie主页的恶意代码 ...
- 用泛型的IEqualityComparer接口去重复项 .
- 使用 IEqualityComparer来过滤PagedCollectionView里的重复数据
- 败给了IEqualityComparer
- C# IEqualityComparer
- 利用Maven快速创建一个简单的spring boot 实例
- 比较IComparer,IComparable,IEqualityComparer,IEquatable之深入接口
- Distinct<TSource>(IEqualityComparer<TSource> comparer) 根据列名来Distinct
- 使用 ESS SDK 快速创建多实例规格伸缩配置
- 使用 ESS SDK 快速创建多实例规格伸缩配置
- 利用Maven快速创建一个简单的spring boot 实例
- C# IEqualityComparer 去重
- Visual Studio IDE改进:快速创建云程序
- 用泛型的IEqualityComparer<T>接口去重复项