既前兩篇之后,這一篇我們討論通過struct 關鍵字自定義值類型。
在第一篇已經討論過值類型的優勢,節省空間,不會觸發Gargage Collection等等。
在對性能要求比較高的場景下,通過struct代替類是不錯的選擇。
那么,比如我們定義一個Point 類型,里面包含兩個左邊X, Y。
public struct Point { public int X; public int Y; public Point(int x, int y) { X = x; Y = y; } }
是不是這樣就OK了呢?
當然不是。因為我們必須盡量避免這個值類型被裝箱。
一個良好的值類型的定義,必須充分考慮到這個值類型的使用場景,然后定義好所需要的成員函數,從而避免有的函數調用不到而將值類型裝箱的情況。比如說,如果這個Point可能會被放到某個容器中,並且排序,那么Point就必須實現接口System.IComparable,實現CompareTo 方法和接口System.IComprable<T>中類型安全的CompareTo 方法。
老趙前輩的博文防止裝箱落實到底,只做一半也是失敗 給了一個非常好的例子,我把它引用過來做一點討論。
博文中的場景是struct所定義的值類型MyKey需要用作字典的鍵。
我們以System.Collections.Hashtable為例,其構造函數為
public Hashtable( int capacity, IEqualityComparer equalityComparer )
這里面IEqualityComparer 是一個接口,用來作為HashTable的比較器。這個接口包含兩個函數:Equals 和 GetHashCode 。
(在System.Collections.Generic.Dictionary,以及其他一些集合的視線中,要求兩個對象為了相等,必須具有相同的哈希碼——CLR via C# 第三版。所以在很多情況下,Equals 和 GetHashCode都是定義值類型必須重寫的兩個方法。)
當我們使用struct 定義的值類型來作為HashTable的key時,因為我們自定義的值類型中沒有提供Equals 和 GetHashCode的實現,因此值類型被裝箱,來調用ValueType的這兩個函數。
那么如何實現Equals 和 GetHashCode這兩個方法呢?
對於GetHashCode方法,我們使用
public override int GetHashCode() {}
來重寫其內容,自定義的計算方式最好能夠做到返回的hash值能均勻分布。
對於equals 呢?如果我們僅僅用如下方法是不夠的
public override bool Equals(object that) {}
因為它提供的是和object類型的比較,我們真正需要的是和同樣類型MyKey的比較。
那么是否再加上這個就夠了?
public bool Equals(MyKey that) {}
確實差不多了,但是我們的MyKey需要指明是實現了哪一個接口。程序在運行時,這個接口中的Equals 方法因為在MyKey中被實現,所以才會直接調用MyKey中的 Equals方法。
原文中實現了IEquatable<MyKey>接口。
如果我們打開的Int32的定義看一看
namespace System {public struct Int32 : IComparable, IFormattable, IConvertible, IComparable<int>, IEquatable<int> { ........ public override int GetHashCode() { return this; } public override bool Equals(object obj) { return obj is int && this == (int)obj; } public bool Equals(int obj) { return this == obj; } .... } }
里面的Equals部分和 MyKey的定義一樣,也是有兩個實現。同時Int32 實現了IEquatable<int>接口。
相關閱讀