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插件的更多相关文章
- 实用的sublime插件集合 – sublime推荐必备插件
Package Control 功能:安装包管理 简介:sublime插件控制台,提供添加.删除.禁用.查找插件等功能 使用:https://sublime.wbond.net/installatio ...
- 非常实用的Sublime插件集合 – sublime推荐必备插件
插件介绍 ***PackageControl*** 功能:安装包管理 简介:sublime插件控制台,提供添加.删除.禁用.查找插件等功能 使用方法: 1.安装好控制台,如有不能正常调用 Packag ...
- HBase指定大量列集合的场景下并发拉取数据时卡住的问题排查
最近遇到一例,HBase 指定大量列集合的场景下,并发拉取数据,应用卡住不响应的情形.记录一下. 问题背景 退款导出中,为了获取商品规格编码,需要从 HBase 表 T 里拉取对应的数据. T 对商品 ...
- Thrift架构~动态Thrift插件的注入
先说AOP 说到注入,大家就会想起来IoC和AOP,确实如些,这一讲中,我们通过unity来实现对thrift插件的动态注入,事实上,这个功能在以后的项目中经常要用到,比如,你将一些功能分发到指定服务 ...
- Sublime Text插件:HTML-CSS-JS Prettify
该插件依赖到nodejs环境 1.安装 在Sublime Text中,按下Ctrl+Shift+P调出命令面板; 输入install 调出 Install Package 选项并回车; 输入prett ...
- Android应用插件式开发解决方法
转自:http://blog.csdn.net/arui319/article/details/8109650 一.现实需求描述 一般的,一个Android应用在开发到了一定阶段以后,功能模块将会越来 ...
- Android应用插件式开发解决方法[转]
一.现实需求描述 一般的,一个Android应用在开发到了一定阶段以后,功能模块将会越来越多,APK安装包也越来越大,用户在使用过程中也没有办法选择性的加载自己需要的功能模块.此时可能就需要考虑如何分 ...
- Android插件简介
/** * @actor Steffen.D * @time 2015.02.06 * @blog http://www.cnblogs.com/steffen */ Android插件简介 Andr ...
- .Net程序员学用Oracle系列(14):子查询、集合查询
1.子查询 1.1.子查询简介 1.2.WITH 子查询 2.集合查询 2.1.UNION 和 UNION ALL 2.2.MINUS 2.3.INTERSECT 2.4.集合运算与 ORDER BY ...
随机推荐
- 人脸识别经典算法一:特征脸方法(Eigenface)
这篇文章是撸主要介绍人脸识别经典方法的第一篇,后续会有其他方法更新.特征脸方法基本是将人脸识别推向真正可用的第一种方法,了解一下还是很有必要的.特征脸用到的理论基础PCA在另一篇博客里:特征脸(Eig ...
- MyBatis学习之路之configuration配置
1.首先讲解的是MyBatis核心配置文件configuration.xml的配置 一个完整的configuration.xml配置顺序如下: properties,settings,typeAlia ...
- 【原创】注册assembly 到GAC
http://msdn.microsoft.com/en-us/library/dkkx7f79.aspx http://stackoverflow.com/questions/2182316/how ...
- EmberJs之使用Ember-Data
写在前面 最近比较忙,换了新工作还要学习很多全新的技术栈,并给自己找了很多借口来不去坚持写博客.常常具有讽刺意味的是,更多剩下的时间并没有利用而更多的是白白浪费,也许这就是青春吧,挥霍吧,这不是我想要 ...
- 水火难容:同步方法调用async方法引发的ASP.NET应用程序崩溃
之前只知道在同步方法中调用异步(async)方法时,如果用.Result等待调用结果,会造成线程死锁(deadlock).自己也吃过这个苦头,详见等到花儿也谢了的await. 昨天一个偶然的情况,造成 ...
- Wix 安装部署(五) Bootstrapper 捆绑安装
Wix的xml配置确实很费劲,忍不住有点像吐槽一下,前四篇完成的功能在Windows Installer中通过配置能很快的弄出来.可惜有很多加了锁的功能在InstallShield Limited E ...
- 年终知识分享——UML、设计模式、设计原则
...
- MySql获取表的字段名称、字段注解、字段类型、字段长度
SELECT COLUMN_NAME as '列名',COLUMN_COMMENT,DATA_TYPE as '字段类型' ,COLUMN_TYPE as '长度加类型' FROM informat ...
- 地址转换协议ARP
地址转换协议ARP 在以太网协议中规定,同一局域网中的一台主机要和另一台主机进行直接通信,必须要知道目标主机的MAC地址.而在TCP/IP协议中,网络层和传输层只关心目标主机的IP地址.这就导致在以太 ...
- easyui使用技巧
1.自定义datagrid字体大小 通过formatter改变字体大小,然后在列中使用: 如下: function formatFontSize(value){ return'<span sty ...