C#集合 -- Equality和Order插件

在前面的文章C#相等性比较C#排序比较中,我已经叙述了类型相等,类型哈希,和类型比较的.NET标准协议。实现了这些协议的类型在一个字典或者列表中也可以正常工作。但是需要注意的是:

  • 只有当类型的Equals方法和GetHashCode方法返回有意义的结果时,该类型才可以作为Dictionary或Hashtable的键
  • 只有当类型实现了IComparable/IComparable<T>才可以作为排序字典或排序列表的键

一个类型的默认相等实现或比较实现典型地反映了该类型最“自然”的那一面。但是,有时候,默认的行为并不是你期望的效果。你可能希望一个string类型的键可以区分大小写;或者你希望一个可排序的客户列表按照客户的邮政编码排序。由于这些原因,.NET Framework定义了一组对应的插入协议,该协议可以实现下面两个目的:

  • 允许你在可替代的相等性行为或可替代的比较行为之间相互切换
  • 允许你使用一个字典或一个排序集合,它们的键的类型内在是不等的或不可比较的

这些协议由下面的接口组成:

IEqualiyComparer和IEqualityComparer<T>

  • 执行插件式相等性比较和哈希
  • 可被Hashtable和Dictionary识别

IComparer和IComparer<T>

  • 执行插件式排序比较
  • 可被排序字典或拍戏集合,以及Array.Sort识别

每个接口都有generic和非generic的版本。IEqualityComparer接口也包含了EqualityComparer的默认的实现。

此外,在Framework 4.0中,还引入了两个新的接口IStructuralEquatable和IStructuralComparable,它们允许结构可以像类或者数组那样执行比较。

IEqualityComparer和EqualityComparer

相等性比较在非默认的相等性和哈希行为上切换,这主要适用于Dictionary类和HashTable类。

回忆一下以哈希表为基础的字典,对于一个指定的键,需要回答下面两个问题:

  • 该键与其他的键是否相同?
  • 该键的哈希码是多少?

实现IEqualityComparer的相等性比较器可以回答上面两个问题

public interface IEqualityComparer<T>
{
bool Equals (T x, T y);
int GetHashCode (T obj);
}
public interface IEqualityComparer // Nongeneric version
{
bool Equals (object x, object y);
int GetHashCode (object obj);
}

为了创建一个自定义比较器,你需要实现上面一个或者两个接口(如果实现了上面连个接口,那么就可以保证最大程度的互操作)。但这么做优点单调,另外一种替换方法是为抽象类EqualityComparer类创建子类,EqualityComparer的定义如下:

public abstract class EqualityComparer<T> : IEqualityComparer,
IEqualityComparer<T>
{
public abstract bool Equals (T x, T y);
public abstract int GetHashCode (T obj);
bool IEqualityComparer.Equals (object x, object y);
int IEqualityComparer.GetHashCode (object obj);
public static EqualityComparer<T> Default { get; }
}

由于EqualityComparer实现连个两个接口,因此你的工作就简化为重写它的两个抽象方法。

Equals方法和GetHashCode与我们在C#相等性比较中所叙述的一样。在下面的例子中,我们定义一个Customer类,它包含两个成员,然后创建一个相等性比较器以比较客户的姓名是否相等。

public class Customer
{
public string LastName;
public string FirstName;
public Customer (string last, string first)
{
LastName = last;
FirstName = first;
}
}

public class LastFirstEqComparer : EqualityComparer <Customer>
{
public override bool Equals (Customer x, Customer y)
{
return x.LastName == y.LastName && x.FirstName == y.FirstName;
}
public override int GetHashCode (Customer obj)
{
return (obj.LastName + ";" + obj.FirstName).GetHashCode();
}
}

为了演示器可以工作,我们创建两个客户实例

Customer c1 = new Customer ("Bloggs", "Joe");
Customer c2 = new Customer ("Bloggs", "Joe");

由于我们没有重写object.Equals,在执行比较时,会执行常规的引用类型比较

Console.WriteLine (c1 == c2); // False
Console.WriteLine (c1.Equals (c2)); // False

如果我们创建一个客户字典实例,且使用默认的相等性比较器对这两个客户进行比较,那么会返回false

var d = new Dictionary<Customer, string>();
d [c1] = "Joe";
Console.WriteLine (d.ContainsKey (c2)); // False

最后,如果我们在创建字典实例时,在构造函数中指定了自定义相等性比较

var eqComparer = new LastFirstEqComparer();
var d = new Dictionary<Customer, string> (eqComparer);
d [c1] = "Joe";
Console.WriteLine (d.ContainsKey (c2)); // True

EqualityComparer<T>.Default

调用EqualityComparer<T>.Default返回一个generic的相等性比较器,使用这个比较器可以替代静态的object.Equals方法。使用这种方式的优点在于,它首先检查类型T是否实现了IEquatble<T>;如果它实现了这个接口,那么就就调用该实现,从而避免了额外的装箱操作。这特别适用于generic的方法:

static bool Foo<T> (T x, T y)
{
bool same = EqualityComparer<T>.Default.Equals (x, y);
...
}

IComparer和Comparer

对于排序字典和集合,比较器还经常用于替代自定义排序。

请注意,比较器对于非排序字典和哈希表没有作用,这位非排序字典和哈希表需要IEqualityComperer去获取哈希码。类似地,一个相等性比较器在排序字典和集合中也不会有用。

下面是IComparer接口的定义

public interface IComparer
{
int Compare(object x, object y);
}
public interface IComparer <in T>
{
int Compare(T x, T y);
}

如果,你要使用相等性比较,你可以继承抽象类Comparer<T>,而不是实现ICompare接口或/和ICompare<T>接口。

public abstract class Comparer<T> : IComparer, IComparer<T>
{
public static Comparer<T> Default { get; }
public abstract int Compare (T x, T y); // Implemented by you
int IComparer.Compare (object x, object y); // Implemented for you
}

下面的列子演示了一个类wish,一个比较器通过wish类的pripority属性进行排序

class Wish
{
public string Name;
public int Priority;
public Wish (string name, int priority)
{
Name = name;
Priority = priority;
}
}
class PriorityComparer : Comparer <Wish>
{
public override int Compare (Wish x, Wish y)
{
if (object.Equals (x, y)) return 0; // Fail-safe check
return x.Priority.CompareTo (y.Priority);
}
}

调用object.Equals方法确保了我们的比较结果不会与Equals方法矛盾。在上面的例子中,调用静态方法object.Equals方法比调用x.Equals方法好,这是因为x可能是null。

下面的代码演示了如何使用PriorityComparer来排序一个列表

var wishList = new List<Wish>();
wishList.Add (new Wish ("Peace", 2));
wishList.Add (new Wish ("Wealth", 3));
wishList.Add (new Wish ("Love", 2));
wishList.Add (new Wish ("3 more wishes", 1));
wishList.Sort (new PriorityComparer());
foreach (Wish w in wishList) Console.Write (w.Name + " | ");
// OUTPUT: 3 more wishes | Love | Peace | Wealth |

在下面的例子中,SurnameComparer允许你对电话簿列表的联系人数据按照姓进行排序

class SurnameComparer : Comparer <string>
{
string Normalize (string s)
{
s = s.Trim().ToUpper();
if (s.StartsWith ("MC")) s = "MAC" + s.Substring (2);
return s;
}
public override int Compare (string x, string y)
{
return Normalize (x).CompareTo (Normalize (y));
}
}

var dic = new SortedDictionary<string,string> (new SurnameComparer());
dic.Add ("MacPhail", "second!");
dic.Add ("MacWilliam", "third!");
dic.Add ("McDonald", "first!");
foreach (string s in dic.Values)
Console.Write (s + " "); // first! second! third!

StringComparer

StringComparer是一个预定义的插件式类,用于字符串的相等性比较和排序比较,并允许你指定语言和是否区分大小写。StringComparer实现了IEqualityComparer和IComparer接口(以及它们的Generic类型接口)。因此,它可以用于任何类型的字典或者排序集合。它的定义如下

public abstract class StringComparer : IComparer, IComparer <string>,IEqualityComparer,
IEqualityComparer <string>
{
public abstract int Compare (string x, string y);
public abstract bool Equals (string x, string y);
public abstract int GetHashCode (string obj);
public static StringComparer Create (CultureInfo culture,
bool ignoreCase);
public static StringComparer CurrentCulture { get; }
public static StringComparer CurrentCultureIgnoreCase { get; }
public static StringComparer InvariantCulture { get; }
public static StringComparer InvariantCultureIgnoreCase { get; }
public static StringComparer Ordinal { get; }
public static StringComparer OrdinalIgnoreCase { get; }
}

由于StringComparer是抽象类,所以你需要通过它的静态方法或属性获取实例。StringComparer.Ordinal是字符串相等性比较的默认行为;StringComparer.CurrentCulture是字符串排序的默认行为。

在下面的例子中,创建了一个有序的区分大小写的字典,因为dict[“Joe”]和dict[“JOE”]是相等的

var dict = new Dictionary<string, int> (StringComparer.OrdinalIgnoreCase);

在下面的例子中,名字数组使用澳洲英语排序

string[] names = { "Tom", "HARRY", "sheila" };
CultureInfo ci = new CultureInfo ("en-AU");
Array.Sort<string> (names, StringComparer.Create (ci, false));

最后一个例子则是区分文化的SurnameComparer

class SurnameComparer : Comparer <string>
{
StringComparer strCmp;
public SurnameComparer (CultureInfo ci)
{
// Create a case-sensitive, culture-sensitive string comparer
strCmp = StringComparer.Create (ci, false);
}
string Normalize (string s)
{
s = s.Trim();
if (s.ToUpper().StartsWith ("MC")) s = "MAC" + s.Substring (2);
return s;
}
public override int Compare (string x, string y)
{
// Directly call Compare on our culture-aware StringComparer
return strCmp.Compare (Normalize (x), Normalize (y));
}
}

IStructuralEquatable和IStructuralComparable

在前面的章节中,我们提到:结构类型默认实现结构比较;如果结构的成员相等,那么两个结构就是相等的。但是,有时候,如果结构也使用插件式结构相等性比较器和结构排序比较器,那将会非常有用。因此,Framework 4.0引入了两个新的接口以实现该目的

这两个接口的定义如下:

public interface IStructuralEquatable
{
bool Equals (object other, IEqualityComparer comparer);
int GetHashCode (IEqualityComparer comparer);
}
public interface IStructuralComparable
{
int CompareTo (object other, IComparer comparer);
}

你传入的IEqualityComparer/IComparer参数,可以用于复合对象中的每个元素。我们可以通过使用array和tuple类型来演示这点,因为它们都实现了这些接口。在下面的例子中,我们比较两个数组是否相等。第一个数组使用Equals方法比较,第二个使用IStructureEquatable进行比较

int[] a1 = { 1, 2, 3 };
int[] a2 = { 1, 2, 3 };
IStructuralEquatable se1 = a1;
Console.Write (a1.Equals (a2)); // False
Console.Write (se1.Equals (a2, EqualityComparer<int>.Default)); // True

下面的是另外一个例子

string[] a1 = "the quick brown fox".Split();
string[] a2 = "THE QUICK BROWN FOX".Split();
IStructuralEquatable se1 = a1;
bool isTrue = se1.Equals (a2, StringComparer.InvariantCultureIgnoreCase);

Tuples按照同样的方式工作

var t1 = Tuple.Create (1, "foo");
var t2 = Tuple.Create (1, "FOO");
IStructuralEquatable se1 = t1;
bool isTrue = se1.Equals (t2, StringComparer.InvariantCultureIgnoreCase);
IStructuralComparable sc1 = t1;
int zero = sc1.CompareTo (t2, StringComparer.InvariantCultureIgnoreCase);

而tuples唯一不同的是,它默认的相等性比较和排序比较都使用了结构比较器

C#集合 -- Equality和Order插件

时间: 2024-10-11 02:22:05

C#集合 -- Equality和Order插件的相关文章

非常实用的Sublime插件集合 – sublime推荐必备插件

插件介绍 ***PackageControl*** 功能:安装包管理 简介:sublime插件控制台,提供添加.删除.禁用.查找插件等功能 使用方法: 1.安装好控制台,如有不能正常调用 Package Control,可以参考上一篇文章内容解决. 解决方案: https://www.cnblogs.com/show2008/p/10882891.html 2.启动插件:按快捷方式 Ctrl+Shift+P  打开 Package Control 窗口(左图) 输入查询 Install Pack

子查询、集合查询

子查询.集合查询 1.子查询 1.1.子查询简介 1.2.WITH 子查询 2.集合查询 2.1.UNION 和 UNION ALL 2.2.MINUS 2.3.INTERSECT 2.4.集合运算与 ORDER BY 3.DISTINCT 子句 3.1.普通用法 3.2.做聚合函数的参数 4.总结 1.子查询 1.1.子查询简介 子查询是一个嵌套在 SELECT.INSERT.UPDATE 或 DELETE 语句或其他子查询中的查询.任何允许使用表达式的地方都可以使用子查询,换句话说,子查询几

Jmeter拓展插件(jmeter-plugins)

Jmeter是一款开源的性能测试工具,纯java编写,体积小,功能强大,基本可以满足性能测试需求.另Jmeter还右一系列的插件来增强其功能,插件地址jmeter-plugins.org.插件现在有5个,分别是Standard set,Extras set,Extras with Libs set,WebDriver set,Hadoop set. Standard Set(标准插件集) 基本的插件,用于满足日常需求.不需要第三方jar包,地址http://jmeter-plugins.org/

Android插件简介

/** * @actor Stafen.D * @time 2015.02.06 * @blog http://www.cnblogs.com/stafen */ Android插件简介 Android下,默认的情况是,每个apk相互独立的,基本上每个应用都是一个dalvik虚拟机,都有一个uid,再配合上linux本身的权限机制,使得apk互通很难直接进行.但作为一个独立应用的集成,不管多少个apk,都可以并为一个单独的dalvik虚拟机,直观的反映给开发人员就是在shell下列出进程,那几个

Android应用插件式开发解决方法[转]

一.现实需求描述 一般的,一个Android应用在开发到了一定阶段以后,功能模块将会越来越多,APK安装包也越来越大,用户在使用过程中也没有办法选择性的加载自己需要的功能模块.此时可能就需要考虑如何分拆整个应用了. 二.解决方案提出 一般有两种方式,一种是将应用按照功能分拆成多个应用,用户需要哪个就下载哪个,都需要就都下载.应用之间,可以在代码层面做一定的关联,以共享部分信息.另一种方式,类似于其他平台插件的方式,用户可以在主应用中可以选择性的下载需要的插件,不需要该功能,则不需要下载. 第一种

.NET深入解析LINQ框架(四:IQueryable、IQueryProvider接口详解)

阅读目录: 1.开篇介绍 2.扩展Linq to Object (应用框架具有查询功能) 2.1.通过添加IEnumerable<T>对象的扩展方法 2.2.通过继承IEnumerable<T>接口 2.3.详细的对象结构图 3.实现IQueryable<T> .IQueryProvider接口 3.1.延迟加载IEnumertor<T>对象(提高系统性能) 3.2.扩展方法的扩展对象之奥秘(this IQueryable<TSource> so

Maven的几个核心概念

POM (Project Object Model) 一个项目所有的配置都放置在 POM 文件中:定义项目的类型.名字,管理依赖关系,定制插件的行为等等.比如说,你可以配置 compiler 插件让它使用 java 1.5 来编译. 示例的 POM: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  

SQL Server2012 T-SQL基础教程--读书笔记(5-7章)

SQL Server2012 T-SQL基础教程--读书笔记(5-7章) SqlServer T-SQL 示例数据库:点我 Chapter 05 表表达式 5.1 派生表 5.1.1 分配列别名 5.1.2 使用参数 5.1.3 嵌套 5.1.4 多个引用 5.2 公用表表达式 5.2.1 分别列别名 5.2.2 使用参数 5.2.3 定义多个CTE 5.2.4 CTE的多次引用 5.2.5 递归CTE 5.3 视图 5.3.1 视图和ORDER BY 子句 5.3.2 视图选项 5.4 内嵌表

有关eclipse for java ee版本遇到的坑( Context initialization failed)

这几天把以前网上看的视频的源代码拷贝到eclipse下面进行学习,当时用的是eclipse-jee-neon-M4a-win32-x86_64这个版本的eclipse,因为它本身集合了web开发插件,谁知道弄好后,发现他需要运行在java8的版本下面,后来就下载安装java8,谁知道一切准备就绪后,发现我的项目无法运行,一直再报 严重: Context initialization failedjava.lang.IllegalArgumentException at org.springfra