两年前,我写了篇文章《快速创建 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. 一次部署HTTPS的相关事件引发的思考

    前言: 上周五快要下班的时候,突然收到通知客户希望了解一下部署HTTPS的流程,这种事情谁听了都会有几分诧异的.因为这件事虽然和工作有一定的相关度,但平时不会走这个方向,实际上也较少接触.此外,客户手 ...

  2. Android开发—已root的手机获取data路径

    开发android的时候,尽管手机已经root但是DDMS中还是没有data/data路径怎么办? 可以用cmd命令提示符为逐个文件夹设置权限: 打开cmd,输入 adb shell  回车—> ...

  3. iOS 杂笔-25(不要用copy修饰NSMutableString)

    iOS 杂笔-25(不要用copy修饰NSMutableString) 首先对题目进行简单的解释,我所说的不要用copy修饰NSMutableString不是说完全不可以用.但是要清楚一点,既然使用N ...

  4. android:使用Messenger进行进程间通信(二)

    //继续完善音乐播放器demo 相关文章: android:使用Messenger进行进程间通信(一):http://www.cnblogs.com/happyhacking/p/5318418.ht ...

  5. Android中Listview点击item不变颜色以及设置listselector 无效

    Android中Listview点击item不变颜色以及设置listselector 无效 这是同一个问题,Listview中点击item是会变颜色的,因为listview设置了默认的listsele ...

  6. footer置底

    html代码: <div class="container"> <div cass="header"></div> < ...

  7. Lucene 时间排序

    在Lucene4.4中,想要实现搜索结果按照时间倒序的效果:如果两个文档得分相同,那么就按照发布时间倒序排列:否则就按照分数排列.这种效果在Lucene4.6中实现起来极其简单,直接利用search接 ...

  8. Windows下FFmpeg各版本库文件下载

    ffmpeg主要是基于linux开发,当然它也支持windows,不过并不支持visual studio系列IDE(因为它使用了大量C99特性,而vs不支持C99). 要想在windows上使用 一可 ...

  9. 【转】理解Java Integer的缓存策略

    本文将介绍 Java 中 Integer 缓存的相关知识.这是 Java 5 中引入的一个有助于节省内存.提高性能的特性.首先看一个使用 Integer 的示例代码,展示了 Integer 的缓存行为 ...

  10. Android Stduio 发生 Process 'command 'somePath:java.exe'' finished with non-zero exit value 2 异常的解决办法

    有时你会发现,在你使用Android Studio 进行编译的时候提示: Error:Execution failed for task ':demo:dexDebug'.> com.andro ...