CLR via C#關於泛型(Generics )的摘錄
泛型,是CLR和編程語言提供的一種特殊機制,它支持另一種形式的代碼重用,即“算法重用”。
簡單的說,開發人員先定義好一個算法,比如排序、搜索、交換、比較或者轉換等。但是,定義算法的開發人員並不設改算法要操作什麽數據類型:改算法可廣發地應用於不同類型的對象。然後,另一個開發人員只要指定了算法要操作的具體數據類型,就可以開始使用這個現成的算法了。例如,可用一個排序算法來操作Int32 和String等類型的對象。
1、大多數算法都封裝在一個類型中,CLR允許創建泛型引用類型和泛型值類型,但不允許創建泛型枚舉類型。除此之外,CLR還允許創建泛型接口和反省委托。所以,CLR允許在引用類型、值類型或接口中定義泛型方法。
private static void SomeMethod() { // Construct a List that operates on DateTime objects List<DateTime> dtList = new List<DateTime>(); // Add a DateTime object to the list dtList.Add(DateTime.Now); // No boxing 不需要裝箱 // Add another DateTime object to the list dtList.Add(DateTime.MinValue); //No boxing 不需要裝箱 // Attempt to add a String object to the list dtList.Add("1/1/2004"); // Compile-time error 編譯時錯誤 // Extract a DateTime object out of the list DateTime dt = dtList[0]; // No cast required 不需要轉型 }
2、泛型的優勢:
- 源代碼保護:使用一個泛型算法的開發人員不需要訪問算法的源代碼。
- 類型安全:將一個泛型算法應用於一個具體的類型時,保證只有與指定數據類型兼容的對象才能同算法使用。若試圖使用不兼容類型的一個對象,會造成編譯時錯誤,或在運行時拋出異常。
- 更加清晰的代碼:由於編譯器強制類型的安全性,減少了源代碼中必須進行的轉型次數。
- 更佳的性能:減少不必要的裝箱、拆箱;由於不再需要轉型,所以CLR不必檢查嘗試的一次轉型操作是否類型安全,提高代碼的運行速度。
3、Framework類庫中的泛型 泛型最明顯的應用就是集合類。FCL已經定義了幾個泛型集合類。其中大多數都在System.Collections.Generic和System.Collection.ObjectModel命名空間中。
4、泛型類型和繼承: 泛型類型仍然是類型,所以它能從其他任何類型派生。由於List<T>是從Objec派生的,所以List<String> 和List<Guid>也從Object派生。
5、泛型接口:泛型的主要作用就是定義泛型的引用類型和值類型。然而,對泛型接口的支持對CLR來說也很重要。沒有泛型接口,每次試圖使用一個非泛型接口(如IComparable)來操縱一個值類型,都會發生裝箱,而且會失去編譯時的類型安全性。
以下泛型接口定義時FCL的一部分(在System.Collections.Generic 命名空間中):
public interface IEnumerator<T> : IDisposable, IEnumerator { T Current { get; } }
下面的示例類型實現了上述泛型接口,而且指定了類型實參。註意,Triangle對象可枚舉一組Point對象。還要註意,Current屬性具有Point數據類型。
internal sealed class Triangle : IEnumerator<Point> { private Point[] m_vertices; // IEnumerator<Point>‘s Current property is of type Point public Point Current { get { ... } } ... }
6、泛型委托:CLR支持泛型委托,目的是保證任何類型的對象都能以一種類型安全的方式傳給一個回調方法。此外,泛型委托允許一個值類型實例在傳給一個回調方法時不執行任何裝箱處理。
例如,假定像下面這樣定義一個委托泛型:
public delegate TReturn CallMe<TReturn, TKey, TValue>(TKey key,TValue value);
編譯器會將它轉成一個類,該類在邏輯上可以這樣表示:
public sealed class CallMe<TReturn, TKey, TValue> : MulticastDelegate { public CallMe(Object object, IntPtr method); public virtual TReturn Invoke(TKey key, TValue value); public virtual IAsyncResult BeginInvoke(TKey key, TValue value, AsyncCallback callback, Object object); public virtual TReturn EndInvoke(IAsyncResult result); }
7、泛型方法:定義泛型類、結構或接口時,這些類型中定義的任何方法都可引用由類型指定的一個類型參數。類型參數可以作為方法的參數,作為方法的返回值,或者作為方法內部定義的一個局部變量來使用。
internal sealed class GenericType<T> { private T m_value; public GenericType (T value) {m_value=value;} public TOutput Converter<TOutput>(){ TOutput result=(TOutput) Convert.ChangeType(m_value ,typeof(TOutput)); return result; //返回將value轉換成TOutput類型之後的結果 } }
在這個例子中,GenericType類定義了自己的類型參數(T),Converter 方法也定義了自己的類型參數(TOutput)。這樣的GenericType可以處理任何類型。Convert方法能將m_value字段引用的對象轉換成任意類型——具體取決於傳給它的類型實參是什麽。泛型方法的存在,為開發人員提供了極大的靈活性。
Swap方法:
private static void Swap<T>(ref T o1, ref T o2){ T temp=o1; o1=o2; o2=temp; }
可以在代碼中像這樣調用Swap:
private static void CallingSwap() { Int32 n1 = 1, n2 = 2; Console.WriteLine("n1={0}, n2={1}", n1, n2);//1,2 Swap<Int32>(ref n1, ref n2); Console.WriteLine("n1={0}, n2={1}", n1, n2);//2,1 String s1 = "Aidan", s2 = "Grant"; Console.WriteLine("s1={0}, s2={1}", s1, s2);//Aidan,Grant Swap<String>(ref s1, ref s2); Console.WriteLine("s1={0}, s2={1}", s1, s2);//Grant,Aidan }
8、其他可驗證問題
1)、泛型類型變量的轉型:將一個泛型類型變量轉型為另一個類型是非法的,除非將其轉型為與一個約束兼容的類型。
private static void CastingAGenericTypeVariable1<T>(T obj) { Int32 x = (Int32) obj; // Error 無法將類型”T"轉換為"int" String s = (String) obj; // Error 無法將類型“T"轉換為"string" }
上述兩行會造成編譯器報錯,因為T可能是任意類型,無法保證能成功轉型。為了修改上述代碼使其能編譯通過,可以先轉型為Object:
private static void CastingAGenericTypeVariable2<T>(T obj) { Int32 x = (Int32) (Object) obj; // No error String s = (String) (Object) obj; // No error }
雖然代碼現在能夠編譯,但CLR仍有可能在運行時拋出一個InvalidCastException異常。
如果試圖轉型為一個引用類型,還可以使用C#的as操作符。下面對代碼進行了修改,將操作符與Sting配合使用(因為Int32是一個值類型):
private static void CastingAGenericTypeVariable3<T>(T obj) { String s = obj as String; // No error }
2)、將一個泛型類型變量設為默認值
將泛型類型變量設為null是非法的,除非將泛型類型約束成一個引用類型。
private static void SettingAGenericTypeVariableToNull<T>() { T temp = null; // CS0403 – Cannot convert null to type parameter ‘T‘ because it could // be a non-nullable value type. Consider using ‘default(T)‘ instead
//無法將null轉換為類型參數“T",因為它可能是不為null的值類型。請考慮改用default(‘T‘)
}
由於未對T進行約束,所以它可能是一個值類型,而將值類型的一個變量設為null是不可能的。如果T被約束成引用類型,將temp設為null就是合法的,代碼能順利編譯並運行。
private static void SettingAGenericTypeVariableToDefaultValue<T>() { T temp = default(T); // OK }
以上代碼中的default關鍵字告訴C#編譯器和CLR的JIT編譯器,如果T是一個引用類型,就將temp設為null;如果T是一個值類型,就將temp的所有位設為0。
3)、將一個泛型類型變量與null進行比較
無論泛型類型是否被約束,使用==或!=操作符將一個泛型類型變量與null進行比較都是合法的。
private static void ComparingAGenericTypeVariableWithNull<T>(T obj) { if (obj == null) { /* Never executes for a value type */ } //對於值類型來說,永遠不會執行到判斷裏 }
註:如果T被約束成為一個struct,C#編譯器會報錯。值類型的變量不能與null進行比較,因為結果始終是相同的。
4)、兩個泛型變量相互比較
如果泛型類型參數不是一個引用類型,對同一個泛型類型的兩個變量進行比較是非法的。
private static void ComparingTwoGenericTypeVariables<T>(T o1, T o2) { if (o1 == o2) { } // Error }
在這個例子中,T為進行約束。雖然兩個引用類型的變量相互比較是合法的,單兩個值類型的變量相互比較是非法的,除非值類型重載了 ”==“ 操作符。
不允許將類型參數約束成一個具體的值類型,因為值類型隱式密封,不可能存在從值類型派生的類型。如果允許將類型參數約束成一個具體的值類型,那麽泛型方法會被約束為只支持該具體類型,這還不如寫一個非泛型的方法呢!
5)、泛型類型變量作為操作數使用
最後要註意的是,將操作符應用於泛型類型的操作數,會出現大量的問題。不能將這些操作符(+,-,*,/)應用於泛型類型的變量,因為編譯器在編譯時無法確定類型。
private static T Sum<T>(T num) where T : struct { T sum = default(T) ; for (T n = default(T) ; n < num ; n++) sum += n; return sum; }
將T約束成為一個struct,而且使用default(T)將sum 和n初始化為0.編譯仍會報錯。運算符”<","++","+="無法應用於“T"(和)”T"類型的操作數
CLR via C#關於泛型(Generics )的摘錄