于快速创建 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>,也可以不用。当然,越简洁越好。

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

于快速创建 IEqualityComparer<T> 实例的类 Equality<T>的更多相关文章

  1. 快速创建 IEqualityComparer 实例:改进

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

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

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

  3. 工厂模式,根据ID创建对应的实例类

    工厂模式,根据ID创建对应的实例类 // // main.cpp // TestCPP1 // // Created by bianchx on 15/4/27. // Copyright (c) 2 ...

  4. 快速创建SpringBoot2.x应用之工具类自动创建web应用、SpringBoot2.x的依赖默认Maven版本

    快速创建SpringBoot2.x应用之工具类自动创建web应用简介:使用构建工具自动生成项目基本架构 1.工具自动创建:http://start.spring.io/ 2.访问地址:http://l ...

  5. 应用DriverManager类创建sqlserver数据库连接实例 JSP中使用数据库

    JSP中使用数据库 1.JDBC介绍 java数据库连接(java Database Connectivity ,JDBC)是一种用于执行SQL语句的JavaAPI ,由一组使用java编程语言编写的 ...

  6. vs里根据json快速创建对应类的方法

    有时候,我们在调用别人接口的时候,服务端返回了一个json格式的字符串,我们要获取json里面的数据的话一般有两种方式: 1.通过正则 2.反序列化成一个对象 第一种方式这里不再多说,主要说一下第二种 ...

  7. Foundation框架 - 快速创建跨平台的网站页面原型

    API参考:http://foundation.zurb.com/docs/ 作为网页设计和开发人员,我们面临着以下几个严峻的问题: 每天,人们用来上网的设备种类和数量都在不断上升. 为每种设备设计开 ...

  8. 快速创建显示数字数据的动画——CountUp.js

    由于项目需求,需要写一个数字增/减量的动画特效,最后找到了CountUp.js CountUp.js是一个无依赖,轻量级的JavaScript“类”,可用于快速创建以更有趣的方式显示数字数据的动画. ...

  9. 十分钟快速创建 Spring Cloud 项目

    一般来说,Intelij IDEA 可以通过 Maven Archetype 来快速生成Maven项目,其实 IDEA 集成了 Spring 官方提供的 Spring Initializr,可以非常方 ...

随机推荐

  1. Oracle语句优化1

    Oracle语句优化1 优化就是选择最有效的方法来执行SQL语句.Oracle优化器选择它认为最有效的     方法来执行SQL语句.         1. IS   NULL和IS   NOT   ...

  2. Ubuntu 14.04 LAMP搭建(Apache 2.47+MySQL 5.5+PHP5.5)

    原文:Ubuntu LAMP搭建 为了数据库课程设计,只好自己搭一个数据库系统,采用LAMP方式. 一.安装 1.安装Apache sudo apt-get install apache2 Apach ...

  3. Lichee (六) 优化配置的微内核

    我们的分析<Lichee(二) 在sun4i_crane平台下的编译 >的时候.竟然没有一个步骤是在配置内核 make ARCH=arm menuconfig 细致的读过的代码的会发现,在 ...

  4. hdu Simpsons’Hidden Talents(kmp)

    Problem Description Homer: Marge, I just figured out a way to discover some of the talents we weren’ ...

  5. CSS定位:几种类型的position定位的元素

    当人们刚接触布局的时候都比较倾向于使用定位的方式.因为定位的概念看起来好像比较容易掌握.表面上你确切地指定了一个块元素所处的位置那么它就会坐落于那里.可是定位比你刚看到的时候要稍微复杂一点.对于定位来 ...

  6. Jquery实现 TextArea 文本框根据输入内容自动适应高度

    原文 Jquery实现 TextArea 文本框根据输入内容自动适应高度 在玩微博的时候我们可能会注意到一个细节就是不管是新浪微博还是腾讯微博在转发和评论的时候给你的默认文本框的高度都不会很高,这可能 ...

  7. 关于微软公有云Azure会计标准

    前几年.中国的云计算项目往往搞成了房地产项目.大搞形"象project",没有实质性的内容.云计算老总成了房地产大老板,国内业界是在胡闹! 现今,世纪互联与微软(中国)联手搞公有云 ...

  8. 玩转Vim-札记

    玩转Vim-札记 距上篇博文已有一周有余,上次主要介绍了编辑器之神Vim的起源.安装并介绍了两种模式以及一些简单的操作.本次将继续对Vim的使用进行介绍. 登堂入室 首先接着说移动吧: 1 0 → 数 ...

  9. 信号量机制DOWN操作和UP操作的详细说明

    DOWN操作:linux内核.信号DOWN例如,下面的操作: void down(struct semaphore *sem); //不间断 int down_interruptible(struct ...

  10. iOS、真机调试

    Xcode中IOS.真机测试 一.购买开发者账号(需要有信用卡.每年支付$99.0) 二.直接淘宝购买一个.用于测试,但是不能上传App 1.获取手机的UUID(Identifier xxxxxx9e ...