两年前,我写了篇文章《快速创建 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. [原][C#][winForm]分级基金折溢价WinForm网络计算器

    分级基金折溢价WinForm网络计算器 通过子/母基金代码,从 [ 东方财富网,天天基金网,新浪 ] 抓取分级基金的子母基金数据(代码,名称,净值,价格), 并计算出子基金(A基金,B基金)以及母基金 ...

  2. HTML DOM 教程

    HTML DOM DOM 教程 DOM 简介 DOM 节点 DOM 方法 DOM 属性 DOM 访问 DOM 修改 DOM 内容 DOM 元素 DOM 事件 DOM 导航 一,HTML DOM 简介 ...

  3. 1-1 node 基础

    1.什么是nodejs?   简单的说 Node.js 就是运行在服务端的 JavaScript. Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台. Node.js ...

  4. Linux入侵检测常用命令

    find / -mtime 0 #0代表目前时间,表示从现在开始到24小时以前,有改动过内容的文件全都会被列出来.如果是3天前24小时内,则使用find / -mtime 3 find /etc -n ...

  5. iOS - 详细理解KVC与KVO

    详细理解KVC与KVO 在面试的时候,KVC与KVO有些时候还是会问到的,并且他们都是Objective C的关键概念,在这里我们先做一个简单地介绍: (一)KVC: KVC即指:NSKeyValue ...

  6. shareSDK实现分享操作时只显示英文字体

    今天用shareSDK实现分享操作时,突然发现分享视图只显示英文字体,而之前是显示中文的,以下是自己找到的解决方法: ,首先,找到shareSDk里的“ShareSDKUI_Localizable.s ...

  7. ListView和Adapter的配合使用以及Adapter的重写

    ListView和Adapter的使用   首先介绍一下ListView是Android开发过程中较为常见的组件之一,它将数据以列表的形式展现出来.一般而言,一个ListView由以下三个元素组成: ...

  8. CSS3-03 样式 2

    前言 在上一篇的博客中,阐述了 CSS 盒模型中的 Margin.Border.Padding 三个样式.但是总觉得,这些东西好像是 HTML 元素的包装样式,真正的要点是 HTML 元素(即:盒模型 ...

  9. Java中的阻塞队列

    1. 什么是阻塞队列? 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列.这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空.当队列满时,存储元素的线程会等待队列可用 ...

  10. Linux监控工具介绍系列——free

    在Linux系统中,我们查看.监控系统内存使用情况,一般最常用的命令就是free.free命令其实非常简单,参数也非常简单,但是里面很多知识点未必你都掌握了.下面总结一下我所了解的free命令.如有不 ...