两年前,我写了篇文章《快速创建 IEqualityComparer<T> 和 IComparer<T> 的实例》,文中给出了一个用于快速创建 IEqualityComparer<T> 实例的类 Equality<T>。

在后来的使用中发现了一些不足,在此进行一些改进,以便更好的使用。原文中的 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
5
6
7
8
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;

请告诉我 b1 和 b2 的值。

问题二,为 null 时 HashCode 应该是什么?

1
2
var h1 = StringComparer.InvariantCulture.GetHashCode(p1.Name);
var h2 = EqualityComparer<Peron>.Default.GetHashCode(p3);

请告诉我 h1 和 h2 的值。

建议大家想下这两个问题,答案就不给出了,自行调试吧。

你的答案和调试得出的结果可能会有出入,如果这样你得好好思考下了。

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
5
6
7
8
9
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
5
6
7
8
9
10
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));
}

这样得出 b4 的值为 true.

我不赞同这种方式,我的观点是:“p=>p.Name”指定使用 Person 的 Name 进行相等比较,Person若不存在(值为 null), Name 更不存在,也谈不上相等,所以应返回 false。

当然还有另一种想法,Person 不存在,没法比,应该抛出异常。

第 33 行代码

也可以写成:

1
return RuntimeHelpers.GetHashCode(null);

RuntimeHelpers 类在 System.Runtime.CompilerServices 命名空间下,我在反编译 Object 时,在 GetHashCode() 方法中发现了它。

复杂情况下的使用

一位园友问我这样一个问题,如下两个类:

1
2
3
4
5
6
7
8
public class Employee
{
public School School { get; set; }
}
public class School
{
public string City { get; set; }
}

要创建 Employee 的相等比较器,根据其学校(School)的所在城市(City)。不考虑一个 Employee 多个 School 的情况,但要考虑 Employee 的 School 属性为 null 的情况(可能没上过学)。

用以下方式创建:

1
var employeeComparer = Equality<Employee>.CreateComparer(i => i.School.City);

运行时,可能会出错。执行比较时,遇到 Employee 的 School 属性为 null ,便会抛出 NullReferenceException。

一种可行的写法是:

1
2
var companylComparer = Equality<School>.CreateComparer(i => i.City);
var employeeComparer = Equality<Employee>.CreateComparer(i => i.School, companylComparer);

是的,分两步。也许是麻烦了些,不过试想下如果没有 Equality<T> 类的帮助,如果实现这个这个相等比较器?相当麻烦,不信可以试着写下。

简单测试下:

1
2
3
4
5
6
7
8
9
10
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
5
6
7
8
9
10
11
12
13
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; }
}

还是要求创建 Employee 的相等比较器,根据 Employee 的 School 的 City 的 Country 来判断。要考虑各引用属性的为 null 时的情形。

还不过瘾,就再加点难度! Country 比较时不考虑大小写。

嘻嘻,有谁能告诉我如何创建,可以使用本文中的 Equality<T>,也可以不用。当然,越简洁越好。

知道的话,请回复我,非常期待你的参与!

后记

终于赶在最后一分钟完成了2013年最后一篇文章。三更半夜,行文仓促,如有疏漏,请多包涵。

祝大家 2014 年新年快乐!有更大的收获!

快速创建 IEqualityComparer 实例:改进的更多相关文章

  1. 于快速创建 IEqualityComparer<T> 实例的类 Equality<T>

    于快速创建 IEqualityComparer<T> 实例的类 Equality<T> 原文中的 Equality<T> 实现如下: 1 2 3 4 5 6 7 8 ...

  2. Hype-v创建服务器实例

    1.创建虚拟交换机,如下图所示(虚拟交换机,只需要创建一次,后面增加服务器实例的时候,只需要选择这个虚拟交换机就可以了,不用每次都创建) 2.服务器主网络共享给虚拟交换机,如下图所 3.虚拟交换机的I ...

  3. 一分钟在云端快速创建MySQL数据库实例

    本教程将帮助您了解如何使用Azure管理门户迅速创建,连接,配置MySQL 数据库 on Azure.完成本教程后,您将在Azure上拥有一个示例MySQL数据库服务器,并了解如何使用管理门户执行基本 ...

  4. 单机静默安装GI软件并创建ASM实例和ASM磁盘组

    环境:RHEL 6.4 + Oracle 11.2.0.4 需求:单机静默安装GI软件并创建ASM实例和ASM磁盘组,为后续迁移数据库文件到ASM做准备 1. 安装配置GI软件 2. 创建ASM实例 ...

  5. (视频)《快速创建网站》 4.2 完结篇 – 应用运营vs.发射卫星,遥测(Telemetry) 技术

    本文是<快速创建网站>系列的第10篇(完结篇),如果你还没有看过之前的内容,建议你点击以下目录中的章节先阅读其他内容再回到本文.访问本系列目录,请点击:http://devopshub.c ...

  6. (视频) 《快速创建网站》3.4 网站改版3分钟搞定 - WordPress主题安装和备份

    本文是<快速创建网站>系列的第8篇,如果你还没有看过之前的内容,建议你点击以下目录中的章节先阅读其他内容再回到本文. 访问本系列目录,请点击:http://devopshub.cn/tag ...

  7. (视频) 《快速创建网站》 2.3 WordPress初始化和功能简介

    本文是<快速创建网站>系列的第4篇,如果你还没有看过之前的内容,建议你点击以下目录中的章节先阅读其他内容再回到本文. 访问本系列目录,请点击:http://devopshub.cn/tag ...

  8. (视频) 《快速创建网站》2.1 在Azure上创建网站及网站运行机制

    现在让我们开始一天的建站之旅. 本文是<快速创建网站>系列的第2篇,如果你还没有看过之前的内容,建议你点击以下目录中的章节先阅读其他内容再回到本文. 访问本系列目录,请点击:http:// ...

  9. (视频) 《快速创建网站》1. 网站管理平台WordPress & 微软Azure 云计算简介

    网站并不神秘,过节了,在家闲的没事的,自己建个网站玩玩吧.每段视频不超过15分钟,地铁/公交/睡前/醒来看一段,几天之后变身建站专家,找老板加薪去! 在普通人眼里,创建网站是专业开发人员和IT工程师才 ...

随机推荐

  1. 【单页应用巨坑之History】细数History带给单页应用的噩梦

    前言 在我们日常的网页浏览中,我们非常喜欢做一个操作:点击浏览器的前进后退在Ajax技术出现后,有些时候前进后退就会给开发者带来困扰,甚至一些开发者试图去干掉History随着Html5的发展,移动端 ...

  2. Windows8.1系统下让VS2012编译运行IIS Express 64位 调试器

    有时候在window8以上系统中对C#系统进行调试 提示不能使用32位IIS Express,如果每次都采用IIS 在vs代码进行调试的时候很麻烦 下面我们就介绍一下怎么用编译调试64位代码: 网上方 ...

  3. 获取OpenFileDialog的文件名和文件路径

    得到文件名 string fileName = ofd.SafeFileName; 得到路径 string filePath = System.IO.Path.GetDirectoryName(ofd ...

  4. 转发:Chrome 控制台console的用法

    大家都有用过各种类型的浏览器,每种浏览器都有自己的特色,本人拙见,在我用过的浏览器当中,我是最喜欢Chrome的,因为它对于调试脚本及前端设计调试都有它比其它浏览器有过之而无不及的地方.可能大家对co ...

  5. git下载指定版本的代码

    1. git fetch https://github.com/angular/angular.js.git v1.5.8 或 2. git pull https://github.com/angul ...

  6. JAVA模板方法设计模式(从现实生活角度理解代码原理)

    概述: 定义一个功能的框架(骨架),一部分功能是确定的,一部分功能是不确定的,先把确定的部分实现,把不确定的部分延迟到子类中实现. 实现该模式的关键步骤: 第一步:抽象类,实现模板方法,定义功能(确定 ...

  7. Nginx中文详解、配置部署及高并发优化

      一.Nginx常用命令: 1. 启动 Nginx          /usr/local/nginx/sbin/nginxpoechant@ubuntu:sudo ./sbin/nginx2. 停 ...

  8. scrollWidth,clientWidth,offsetWidth的区别

      通过一个demo测试这三个属性的差别. 说明: scrollWidth:对象的实际内容的宽度,不包边线宽度,会随对象中内容超过可视区后而变大. clientWidth:对象内容的可视区的宽度,不包 ...

  9. Mysql数据库的基本概念和架构

    数据库 1.键:主键是表中的标志列.一个键可能由几列组成.可以使用键作为表格之间的引用. CustomerID是Customers表的主键,当它出现在其他表,例如Orders表中的时候就称它为外键. ...

  10. CLR线程概览(下)

    作者:施懿民链接:https://zhuanlan.zhihu.com/p/20866017来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 同步: 托管代码 托管代码可 ...