泛型介面、泛型委派、泛型方法、泛型約束

來源:互聯網
上載者:User

標籤:blog   使用   資料   os   問題   cti   

泛型介面   沒有泛型介面,每次試圖使用一個非泛型介面(如IComparable)來操縱一個實值型別時,都會進行裝箱,而且會丟失編譯時間的型別安全。這會嚴重限制泛型型別的應用。所以,CLR提供了對泛型介面的支援。一個參考型別或實值型別為了實現一個泛型介面,可以具體指定類型實參;另外,一個類型也可以保持類型實參的未指定狀態來實現一個泛型介面。來看一些例子:   以下泛型介面定義是作為FCL的一部分發布的: public interface IEnumerable<T> : IDisposable, IEnumerator {T Current { get; }}  下面這個樣本實現了上述泛型介面,且指定了類型實參: internal sealed class Triangle : IEnumerator<Point> {private Point[] m_vertices;Point Current { get {...} }}  下例實現了相同的泛型介面,但保持類型實參的未指定狀態: internal sealed class ArrayEnumerator<T> : IEnumerator<T> {private T[] m_array;//IEnumerator<T>的Current屬性是T類型T Current { get {...} }}  注意,一個ArrayEnumerator對象可以枚舉一系列T對象(其中的T沒有指定,允許代碼使用泛型ArrayEnumerator類型在以後為T指定一個具體的類型)。 泛型委派   CLR支援泛型委派,目的是保證任何類型的對象都能以一種型別安全的方式傳給一個回調方法。此外,泛型委派允許一個實值型別執行個體在傳給一個回調方法時不執行任何裝箱處理。所謂委託,實際是提供了4個方法的一個類定義:一個構造器、一個Invoke方法、一個BeginInvoke方法和一個EndInvoke方法。如果定義的一個委託類型指定了型別參數,編譯器會定義好委託類的方法。   假定像下面這樣定義了一個泛型委派: 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 TReturn Invoke(TKey key, TValue value);public IAsyncResult BeginInvoke(TKey key, TValue value, AsyncCallback callback, Object object);public TReturn EndInvoke(IAsyncResult result);}  FCL配套提供了許多泛型委派類型,其中大多數都用於集合處理。下面是一些例子: //通常用於操作一個集合項目public delegate void Action<T>(T obj); //通常用於比較兩個集合項目,以進行排序public delegate Int32 Comparison<T>(T x, T y); //通常用於將集合項目從一種類型轉換成另一種類型public delegate TOutput Converter<TInput, TOutput>(TInput input);  FCL還提供了用於事件的一個泛型委派。下面展示了這個委託: public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)    where TEventArgs : EventArgs;泛型方法   定義一個泛型參考型別、實值型別或介面時,這些類型中定義的任何方法都可以參考型別指定的一個型別參數。型別參數可作為方法的參數,作為方法的傳回值,或作為方法內部定義的一個局部變數來使用。然而,CLR還允許一個方法指定它專屬的型別參數。這些型別參數可用於參數、傳回值或局部變數。如下所示: 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 reault;}}  為接受out和ref參數的方法使用泛型型別,將非常有趣,因為作為一個out/ref實參傳遞的變數必須具有與方法參數相同的類型,以避免潛在的對型別安全的破壞。事實上,Interlocked類的Exchange和CompareExchange方法正是考慮到這個原因而提供了泛型重載版本:public static class Interlocked{public static T Exchange<T>(ref T location, T value) where T : class;public static T CompareExchange<T>(ref T location, T value, T comparand) where T : class;}泛型方法和類型介面   C#泛型文法因為涉及大量小於和大於符號,影響到代碼的可讀性。為增強代碼建立、可讀性和可維護性,C#編譯器支援在調用一個泛型方法時進行“類型推導”(type inference)。也就是說,編譯器在調用一個泛型方法的時候自動判斷(或推導)要使用的類型。如以下代碼: private static void Swap<T>(ref T o1, ref o2){T temp = o1;o1 = o2;o2 = temp;} private static void CallingSwapUsingInference(){int n1 = 1, n2 = 2;Swap(ref n1, ref n2); //調用Swap<int> string s1 = "a";object s2 = "b";Swap(ref s1, ref s2); //錯誤,不能推導類型}  一個類型可以定義多個方法,讓其中一個方法接受一個具體的資料類型,讓另一個方法接受一個泛型參數,如下所示: private static void Display(string s){Console.WriteLine(s);} private static void Display<T>(T o){Display(o.ToString()); //調用Display(string)}  下面展示Display方法的一些調用方式: Display("jeff"); //調用Display(string)Display(123); //調用Display<T>(T)Display<string>("ai"); //調用Display<T>(T)  在C#編譯器看來,一個更顯式的匹配總是優先於一個泛型匹配的。所以,Display("jeff")產生了對非泛型的Display方法的調用。對Display的第三個調用明確指定了一個泛型型別實參string,這告訴編譯器不要嘗試去推導類型實參,直接使用指定的類型實參。 泛型和其他成員   在C#中,屬性、索引器、事件、操作符方法、構造器和終結器(finalizer)本身不能有型別參數。然而,它們能在一個泛型型別中定義,而且這些成員中的代碼能使用類型的型別參數。   C#之所以不允許這些成員指定它們自己的泛型型別參數,是因為MS的C#Team Dev認為其他開發人員很少需要將這些成員作為泛型使用。此外,為這些成員添加泛型支援的代碼是相當高的,因為必須為語言設計足夠的文法。 可驗證證性和限制   我們可以利用“約束”來限制能指定成泛型實參類型的類型變數。通過限制類型數量,我們可以對那些類型執行更多的操作。以下是一個 Min方法,它指定了一個約束: public static T Min<T>(T o1, T o2) where T : IComparable<T>{if (o1.CompareTo(o2) < 0) return o1;return o2;}  where關鍵字告訴編譯器為T指定的任何類型都必須實現同一個類型(T)的泛型IComparable介面。現在,當代碼引用一個泛型型別或方法時,編譯器要負責保證類型實參符合指定的約束。如: private static void CallMin(){object o1 = "jeff", o2 = "richter";object oMin = Min<Object>(o1, o2); //Error}  約束可應用於一個泛型型別的型別參數,或應用於一個泛型方法的型別參數。CLR允許基於型別參數名稱或約束來進行重載;只能基於arity來重載類型或方法。樣本如下://可定義以下類型internal sealed class AType {}internal sealed class AType<T> {};internal sealed class AType<T1, T2> {} //error: 與沒有約束的AType<T>衝突internal sealed class AType<T> where T : IComparable<T> {} //error:與AType<T1, T2>衝突internal sealed class AType<T3, T4> {} internal sealed class AnotherType{//可定義以下方法private static void M() {}private static void M<T>() {}private static void M<T1, T2> () {} //error:與沒有約束的M<T>衝突private static void M<T> where T : IComparable<T> {} //error:與M<T1, T2>衝突private static void M<T3, T4> {}}  重寫一個virtual泛型方法時,重寫過的版本必須指定相同數量的型別參數,且這些型別參數會繼承由基類的方法在它們上面指定的約束。事實上,重寫的方法根本不準許在它的型別參數上指定任何約束。然而,型別參數的名稱是可以改變的。 internal class Base{public virtual void M<T1, T2>()where T1 : structwhere T2 : class {}} internal sealed class Derived : Base{public override void M<T3, T4>()where T3 : EventArgs //Errorwhere T4 : class //Error{}}主要約束   型別參數可以指定零個或一個主要約束。主要約束可以是一個參考型別,它標識了一個沒有密封的類。不需要指定以下特殊的參考型別:System.Object、System.Array、System.Delegate、System.MulticastDelegate、System.ValueType、System.Enum或System.Void。   指定一個參考型別約束時,相當於向編譯器承諾:一個指定的類型實參要麼是與約束類型相同的類型,要麼是從約束類型派生的一個類型。如以下樣本: internal sealed class PrimaryConstraintOfStream<T> where T : Stream{public void M(T stream){stream.Close();}}  有兩個特殊的主要約束:class和struct。其中,class約束向編譯器承諾一個指定的類型實參是參考型別。任何類類型、介面類型、委託類型或數群組類型都滿足這個約束。struct約束向編譯器承諾一個指定的類型實參是實值型別,包括枚舉在內的任何實值型別都滿足這個約束。但編譯器和CLR將任何System.Nullable<T>實值型別視為一個特殊類型,而可空的類型不能指定這個約束。原因是Nullable<T>類型將它的型別參數限制為struct,而CLR希望禁止像Nullable<Nullable<T>>這樣的一個遞迴類型。 次要約束   一個型別參數可以指定零個或多個次要約束,次要約束代表的是一個介面類型。由於能指定多個介面約束,所以為類型實參指定的類型必須實現所有介面約束。   還有一種輔助約束稱為“型別參數約束”,有時也稱為“裸類型約束”。這種約束的使用比介面約束少得多。它允許一個泛型型別或方法規定在指定的類型實參之間必須存在一個關係。一個型別參數可以應用零個或多個型別參數約束。下面的樣本示範了如何使用型別參數約束: private static List<TBase> ConvertIList<T, TBase>(IList<T> list) where T : TBase{List<Base> baseList = new List<TBase>(list.Count);for(int index = 0; index < list.Count; index++){baseList.Add(list[index]);}return baseList;}  該方法指定了兩個型別參數,其中T參數由TBase型別參數約束。也就是說不管為T指定什麼類型實參,這個類型都必須相容於為TBase指定的類型實參。下面的方法展示了對ConvertIList的合法和非法調用: private static void CallingConvertIList(){//構造並初始化一個List<string>(它實現了IList<string>)IList<string> ls = new List<string>();ls.Add("A String"); //將IList<string>轉換為一個IList<Object>IList<object> lo = ConvertIList<string, object>(ls); //將IList<string>轉換為一個IList<IComparable>IList<IComparable lc = ConvertIList<string, IComparable>(ls); //將IList<string>轉換為一個IList<IComparable<string>>IList<IComparable<string>> lcs = ConvertIList<string, IComparable<string>>(ls); //將IList<String>轉換成一個IList<string>IList<string> ls2 = ConvertIList<string, string>(ls); //錯誤:將IList<string>轉換成一個IList<Exception>IList<Exception> le = ConvertIList<string, Exception>(ls);}構造器約束   一個型別參數可以指定零個或一個構造器約束。指定構造器約束相當於向編譯器承諾:一個指定的類型實參是實現了一個public無參構造器的一個非抽象類別型。注意,如果同時指定了構造器約束和struct約束,C#編譯器會認為這是一個錯誤,因為這是多餘的:所有實值型別都隱式提供了一個public無參構造器。   下例就使用了構造器約束來限制它的型別參數: internal sealed class ConstructorConstraint<T> where T : new(){public static T Factory(){return new T();}}泛型型別變數的轉型   將一個泛型型別變數轉型為另一個類型是非法的,除非將其轉換為與一個約束相容的類型: private static void CastingAGenericTypeVariable<T>(T obj){int x = (int)obj; //錯誤string s = (string)obj; //錯誤 int x2 = (int)(object)obj; //無錯誤string s2 = (string)(object)obj; //無錯誤 string s3 = obj as string; //無錯誤}將一個泛型型別變數設為預設值   將泛型型別變數設為null是非法的,除非將泛型型別約束成一個參考型別。   我們可以使用default關鍵字將一個參考型別設為null,或將一個實值型別設為0。如下所示: private static void SetDefaultValue<T>(){T temp = default(T);}  T如果為參考型別,temp的值為null;T如果為實值型別,temp的值為0。 將一個泛型型別變數與null進行比較   無論泛型型別是否被約束,使用==或!=操作符將一個泛型變數與null進行比較都是合法的: private static void ComparingWithNull<T>(T obj){if(obj == null){//對於實值型別來說,此處永遠不會執行。}}  如果調用這個方法,並為型別參數傳遞一個實值型別,那麼JIT編譯器知道if語句永遠都不會為true,所以不會為if測試或都大括弧內的代碼產生本地代碼。如果換用!=操作符,JIT編譯器不會為if測試產生代碼(因為它肯定為true),但會為if大括弧內的代碼產生本地代碼。   另外,如果T被約束成一個struct,C#編譯器會報錯。 兩個泛型型別變數的相互比較   如果泛型型別參數不是一個參考型別,那麼對同一個泛型型別的兩個變數進行比較是非法的。 泛型型別變數作為運算元使用   將操作符應用於泛型型別的運算元存在大量問題。編譯器在編譯時間無法確定類型,這意味著不能將任何操作符應用於泛型型別的變數。#asp.net

  

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.