C#集合 -- Equality和Order插件

在前面的文章C#相等性比較C#排序比較中,我已經敘述了類型相等,類型哈希,和類型比較的.NET標準協議。實現了這些協議的類型在一個字典或者列表中也能夠正常工做。可是須要注意的是:html

  • 只有當類型的Equals方法和GetHashCode方法返回有意義的結果時,該類型才能夠做爲Dictionary或Hashtable的鍵
  • 只有當類型實現了IComparable/IComparable<T>才能夠做爲排序字典或排序列表的鍵

一個類型的默認相等實現或比較實現典型地反映了該類型最「天然」的那一面。可是,有時候,默認的行爲並非你指望的效果。你可能但願一個string類型的鍵能夠區分大小寫;或者你但願一個可排序的客戶列表按照客戶的郵政編碼排序。因爲這些緣由,.NET Framework定義了一組對應的插入協議,該協議能夠實現下面兩個目的:數組

  • 容許你在可替代的相等性行爲或可替代的比較行爲之間相互切換
  • 容許你使用一個字典或一個排序集合,它們的鍵的類型內在是不等的或不可比較的

這些協議由下面的接口組成:ide

IEqualiyComparer和IEqualityComparer<T>函數

  • 執行插件式相等性比較和哈希
  • 可被Hashtable和Dictionary識別

IComparer和IComparer<T>ui

  • 執行插件式排序比較
  • 可被排序字典或拍戲集合,以及Array.Sort識別

每一個接口都有generic和非generic的版本。IEqualityComparer接口也包含了EqualityComparer的默認的實現。編碼

此外,在Framework 4.0中,還引入了兩個新的接口IStructuralEquatable和IStructuralComparable,它們容許結構能夠像類或者數組那樣執行比較。spa

 

IEqualityComparer和EqualityComparer

image

相等性比較在非默認的相等性和哈希行爲上切換,這主要適用於Dictionary類和HashTable類。插件

回憶一下以哈希表爲基礎的字典,對於一個指定的鍵,須要回答下面兩個問題:3d

  • 該鍵與其餘的鍵是否相同?
  • 該鍵的哈希碼是多少?

實現IEqualityComparer的相等性比較器能夠回答上面兩個問題code

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

image

對於排序字典和集合,比較器還常常用於替代自定義排序。

請注意,比較器對於非排序字典和哈希表沒有做用,這位非排序字典和哈希表須要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

image

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引入了兩個新的接口以實現該目的

image

這兩個接口的定義以下:

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惟一不一樣的是,它默認的相等性比較和排序比較都使用告終構比較器

相關文章
相關標籤/搜索