前一篇文章介紹了泛型的基本概念(見 http://www.linuxidc.com/Linux/2015-02/113465.htm)。在本文中,我們看一下泛型中兩個很重要的特性:類型約束和類型推斷。
相信你還記得前面一篇文章中的泛型方法,在這個泛型方法中,我們就使用了類型約束。
類型約束(type constraint)進一步控制了可指定的類型實參,當我們創建自己的泛型類型或者泛型方法的時候,類型約束是很有用的。
回到前一篇例子中的泛型方法,這個泛型方法就要求可指定的類型實參必須實現了IComparable接口。
為什麼會有這個約束呢?原因很簡單,因為我們在泛型方法的實現中直接調用T類型的"CompareTo"方法。所以,我們需要通過一個約束來保證T類型都有"CompareTo"方法,也就是說我們要指定的類型實參T要實現IComparable接口。
public static T GetBiggerOne<T>(T itemOne, T itemTwo) where T : IComparable { if (itemOne.CompareTo(itemTwo) > 0) { return itemOne; } return itemTwo; }
經過上面的解釋,大家肯定對約束有了簡單的認識。
在類型約束中,有四種約束可供使用,他們的語法都是基本相同的,約束要放到泛型類型或泛型方法的末尾,並由上下文關鍵字where來引入。同時,約束也可以按照一定的規則組合在一起使用。
下面我們就分別看看可供我們使用的四種類型約束。
引用類型表示為T : class,用於確保指定的類型實參都是引用類型(任何類,接口,數組或委托,以及已知為引用類型的另一個類型參數)。
如果使用引用類型約束,那麼它必須是為類型參數指定的第一個約束。
一個簡單的示例,例如對於下面的聲明:
struct RefSample<T> where T : class { }
有效的封閉類型:
無效的封閉類型:
跟引用類型約束形式類似,值類型約束表示為T : struct,用於確保指定的類型實參都是值類型。
同樣,如果使用值類型約束,那麼它必須是為類型參數指定的第一個約束。
例如對於下面的聲明:
class ValSample<T> where T : struct { }
有效的封閉類型:
無效的封閉類型:
構造函數類型約束表示為T : new(),用於確保所有的類型參數有一個無參數的構造函數,這個構造函數可用於創建類型的實例。這適用於:所有值類型;所有非靜態、非抽象、沒有顯示聲明的構造函數的類;顯示聲明了一個公共無參構造函數的所有非抽象類。
如果使用構造函數類型約束,那麼它必須是為類型參數指定的最後一個約束。
下面用一個例子進行簡單的說明:
public T CreateInstance<T>() where T : new() { return new T(); }
這次例子中是一個泛型方法,約束我們指定的類型實參必須擁有無參數的構造函數,在這種情況下,這個泛型方法就可以返回該類型的一個實例。
所有下面都是有效的調用:
注意,在C#中,所有的值類型都有一個默認的無參數構造函數,所以當我們使用一些組合約束的時候,C#編譯器就會報出一個錯誤,因為這樣的指定是多余的,所有值類型都隱式提供一個無參公共構造函數。
public T CreateInstance<T>() where T: struct, new()
轉型類型約束允許我們指定另一個類型,類型實參必須可以通過一致性、引用或裝箱轉換隱式的轉換為改類型。
根據上面的描述,可以看到轉換類型約束可以有以下一些表示:
下面看幾個例子:
class Sample<T> where T: Stream
有效:Sample<Stream>
無效:Sample<string>
class Sample<T> where T: IDisposable
有效:Sample<SqlConnection >
無效:Sample<StringBuilder>
class Sample<T,U> where T: U
有效:Sample<Stream,IDispsable>
無效:LSample<string,IDisposable>
組合約束就是將前面提到的多種約束集合起來使用。
對於一個類型參數,我們可以使用where關鍵字進行多個約束;對於不同的類型參數,可以有不同的約束,它們分別由單獨的where關鍵字引入。
在組合約束中,有很多組合情況是無效的,下面看一下例子:
在調用泛型方法的時候,我們都需要通過"<>"來指定類型實參,就會顯得代碼比較復雜、冗余。其實,根據方法調用時傳遞的實參類型,可以比較容易的推斷出泛型方法的類型參數應該是什麼。
所以,編譯器就添加了一些"智能",幫我們推斷泛型方法的類型參數,這樣我們在方法調用的時候就可以不用顯示的聲明類型實參。
注意,類型推斷只適用於泛型方法。
看一個簡單的類型推斷的例子:
class Program { static void Main(string[] args) { //Console.WriteLine("The bigger one is {0}", GetBiggerOne<int>(3, 9)); //Console.WriteLine("The bigger one is {0}", GetBiggerOne<string>("Hello", "World")); //讓編譯器進行類型推斷 Console.WriteLine("The bigger one is {0}", GetBiggerOne(3, 9)); Console.WriteLine("The bigger one is {0}", GetBiggerOne("Hello", "World")); Console.Read(); } public static T GetBiggerOne<T>(T itemOne, T itemTwo) where T : IComparable { if (itemOne.CompareTo(itemTwo) > 0) { return itemOne; } return itemTwo; } }
本文中介紹了泛型中的類型約束和類型推斷特性。
在我們使用自定義的泛型類型和泛型方法的時候,如果我們已經發現需要進行一些約束,最好就是直接在聲明泛型類型和方法的時候把約束加上。同時應該注意組合約束中的一系列無效的約束組合。
對於類型推斷,這個特性只適合泛型方法,可以簡化我們調用泛型方法時顯示的聲明類型實參。