在上次的一篇翻譯裡已經簡單介紹了一下C#2.0裡的泛型。這次我們接著來瞭解C#泛型的一些特性。
這次介紹一下泛型的約束(Constraints)。
看一下代碼:
public class Dictionary<K,V>
{
public void Add(K key, V value)
{
if (key.CompareTo(x) < 0) {} // Error, no CompareTo method
}
}
由於K可以任何類型,所以可想而知,key只能使用Object中定義的ToString等方法。代碼在編譯時間就出錯了,因為key可能不含有CompareTo方法。
那麼如何解決呢?
一種容易想到的辦法就是把key轉換成IComparable,那麼代碼就變成了這樣:
public class Dictionary<K,V>
{
public void Add(K key, V value)
{
if (((IComparable)key).CompareTo(x) < 0) {}
}
}
這次沒有編譯時間錯誤了。但是還是要小心,因為實際使用時,如果你使用了一個沒有實現IComparable的類型作參數,就會出現InvalidCastException異常。
為了提供強編譯時間檢查和減少類型轉換,C#2.0泛型引入了約束(Constraints)這個概念,這是一個泛型的可選項。文法是使用where關鍵字指明約束的型別參數,然後加冒號,再加上類,介面,型別參數等。
我們把代碼變成了這樣:
public class Dictionary<K,V> where K: IComparable
{
public void Add(K key, V value)
{
if (key.CompareTo(x) < 0) {}
}
}
這樣就保證了任何為K型別參數提供的類型都實現了IComparable介面。所以我們的key就可以使用CompareTo方法了。
如果我們在使用時提供了沒有實現IComparable介面的類型,就會出現編譯時間錯誤。
我們的約束可以不止是一個介面,可以是多個,還可以是跟類或其他約束混合。
請看下面代碼:
public class EntityTable<K,E>
where K: IComparable<K>, IPersistable
where E: Entity, new()
{
public void Add(K key, E entity)
{
if (key.CompareTo(x) < 0) {}
}
}
對於約束new()可能不好理解。其實這個約束保證了提供給型別參數E的類型必須有一個public,無參的構造器。這樣它就允許泛型類使用new E()建立這個類型的執行個體。
另外指出的是,雖然可以有多個介面作約束,但至多隻能有一個類。
型別參數必須小心地使用。雖然它提供了更強編譯時間類型檢查,但是卻也約束了泛型型別的一些可能的使用方式。比如說,有一個泛型類List<T>約束T實現IComparable介面,這樣就可以在List<T>的Sort方法中比較項了。然而,就不能為那些沒有實現IComparable的類使用IList<T>了,就算Sort方法實際上並沒有調用。
參考文獻:《CSharp 2.0 Specification》