.Net泛型類的學習總結
.Net泛型類的學習首先我們來看看什么是.Net泛型類?帶有“類型參數”的類稱為“泛型類”。那么如果使用.Net泛型類,則可為每個這樣的參數提供“類型變量”,從而從.Net泛型類生成一個“構造類”。之后可以聲明類型為構造類的變量,創建構造類的實例,并將它分配給該變量。除類以外,您還可以定義和使用泛型結構、接口、過程和委托。下面我們就來看看.net泛型類的一些聲明和以及.Net泛型類創建的實例情況。
.Net泛型類聲明
泛型類聲明是一種類的聲明,它需要提供類型實參才能構成實際類型。
類聲明可以有選擇地定義類型形參:
- class-declaration:
- attributesopt
- class-modifiersopt
- class identifier
- type-parameter-listopt
- class-baseopt
- type-parameter-constraints-clausesopt
- class-body ;opt
只有提供了一個 type-parameter-list,才可以為這個類聲明提供 type-parameter-constraints-clauses。
提供了 type-parameter-list 的類聲明是一個泛型類聲明。此外,任何嵌套在泛型類聲明或泛型結構聲明中的類本身就是一個泛型類聲明,因為必須為包含類型提供類型形參才能創建構造類型。
除了明確指出的地方外,泛型類聲明與非泛型類聲明遵循相同的規則。泛型類聲明可嵌套在非泛型類聲明中。
使用構造類型 (constructed type)引用泛型類。給定下面的泛型類聲明
- class List﹤T﹥ {}
List﹤T﹥、List﹤int﹥ 和 List﹤List﹤string﹥﹥ 就是其構造類型的一些示例。使用一個或多個類型形參的構造類型(例如 List﹤T﹥)稱為開放構造類型 (open constructed type)。不使用類型形參的構造類型(例如 List﹤int﹥)稱為封閉構造類型 (closed constructed type)。
泛型類型可根據類型形參的數目進行“重載”;這就是說,同一命名空間或外層類型聲明中的兩個類型聲明可以使用相同標識符,只要這兩個聲明具有不同數目的類型形參即可。
- class C {}
- class C﹤V﹥ {}
- struct C﹤U,V﹥ {} // Error, C with two type parameters defined twice
- class C﹤A,B﹥ {} // Error, C with two type parameters defined twice
類型名稱解析、簡單名稱解析和成員訪問、過程中使用的類型查找規則均會考慮類型形參的數目。
泛型類聲明的基接口必須滿足唯一性規則。
.Net泛型類之類型形參
類型形參可在類聲明中提供。每個類型形參都是一個簡單標識符,代表一個為創建構造類型而提供的類型實參的占位符。類型形參是將來提供的類型的形式占位符。而類型實參是在創建構造類型時替換類型形參的實際類型。
- type-parameter-list:
- ﹤ type-parameters ﹥
- type-parameters:
- attributesopt type-parameter
- type-parameters , attributesopt type-parameter
- type-parameter:
- identifier
類聲明中的每個類型形參在該類的聲明空間中定義一個名稱。因此,它不能與另一個類型形參或該類中聲明的成員具有相同的名稱。類型形參不能與類型本身具有相同的名稱。
類上的類型形參的作用域包括 class-base、type-parameter-constraints-clauses 和 class-body。與類的成員不同,此作用域不會擴展到派生類。在其作用域中,類型形參可用作類型。
- type:
- value-type
- reference-type
- type-parameter
由于類型形參可使用許多不同的實際類型實參進行實例化,因此類型形參具有與其他類型稍微不同的操作和限制。這包括:
·不能直接使用類型形參聲明基類或接口
·類型形參上的成員查找規則取決于應用到該類型形參的約束(如果有)。
·類型形參的可用轉換取決于應用到該類型形參的約束(如果有)。
·如果事先不知道由類型形參給出的類型是引用類型不能將標識null 轉換為該類型。不過,可以改為使用默認值表達式。此外,具有由類型形參給出的類型的值可以使用 == 和 != 與null 進行比較,除非該類型形參具有值類型約束。
·僅當類型形參受 constructor-constraint 或值類型約束的約束時,才能將 new 表達式與類型形參聯合使用。
·不能在屬性中的任何位置上使用類型形參。
·不能在成員訪問或類型名稱中使用類型形參標識靜態成員或嵌套類型。
·在不安全代碼中,類型形參不能用作 unmanaged-type。
作為類型,類型形參純粹是一個編譯時構造。在運行時,每個類型形參都綁定到一個運行時類型,運行時類型是通過向泛型類型聲明提供類型實參來指定的。因此,使用類型形參聲明的變量的類型在運行時將是封閉構造類型。涉及類型形參的所有語句和表達式的運行時執行都使用作為該形參的類型實參提供的實際類型。
.Net泛型類之實例類型
每個類聲明都有一個關聯的構造類型,即實例類型 (instance type)。對于泛型類聲明,實例類型是通過從該類型聲明創建構造類型來構成的,所提供的每個類型實參替換對應的類型形參。由于實例類型使用類型形參,因此只能在類型形參的作用域中使用該實例類型;也就是在類聲明的內部。對于在類聲明中編寫的代碼,實例類型為 this 的類型。對于非泛型類,實例類型就是所聲明的類。下面顯示幾個類聲明以及它們的實例類型:
- class A﹤T﹥ // instance type: A﹤T﹥
- {
- class B {} // instance type: A﹤T﹥.B
- class C﹤U﹥ {}// instance type: A﹤T﹥.C﹤U﹥
- }
- class D {} // instance type: D
.Net泛型類之基規范
類聲明中指定的基類可以是構造類類型。基類本身不能是類型形參,但在其作用域中可以包含類型形參。
- class Extend﹤V﹥: V {}
- // Error, type parameter used as base class
泛型類聲明不能使用 System.Attribute 作為直接或間接基類。
類聲明中指定的基接口可以是構造接口類型。基接口本身不能是類型形參,但在其作用域中可以包含類型形參。下面的代碼演示類實現和擴展構造類型的方法:
- class C﹤U,V﹥ {}
- interface I1﹤V﹥ {}
- class D: C﹤string,int﹥, I1﹤string﹥ {}
- class E﹤T﹥: C﹤int,T﹥, I1﹤T﹥ {}
泛型類聲明的基接口必須滿足唯一性規則。
如果類中的方法重寫或實現基類或接口中的方法,則必須為特定類型提供相應的方法。下面的代碼演示如何重寫和實現方法。對此做了進一步的解釋。
#p#
- class C﹤U,V﹥
- {
- public virtual void M1(U x, List﹤V﹥ y) {}
- }
- interface I1﹤V﹥
- {
- V M2(V);
- }
- class D: C﹤string,int﹥, I1﹤string﹥
- {
- public override void M1(string x, List﹤int﹥ y) {}
- public string M2(string x) {}
- }
.Net泛型類的成員
泛型類的所有成員都可以直接或作為構造類型的一部分使用任何包容類 (enclosing class) 中的類型形參。當在運行時使用特定的封閉構造類型時,所出現的每個類型形參都被替換成為該構造類型提供的實際類型實參。例如:
- class C﹤V﹥
- {
- public V f1;
- public C﹤V﹥ f2 = null;
- public C(V x) {
- this.f1 = x;
- this.f2 = this;
- }
- }
- class Application
- {
- static void Main() {
- C﹤int﹥ x1 = new C﹤int﹥(1);
- Console.WriteLine(x1.f1); // Prints 1
- C﹤double﹥ x2 = new C﹤double﹥(3.1415);
- Console.WriteLine(x2.f1); // Prints 3.1415
- }
- }
在實例函數成員中,類型 this 是包含這些成員的聲明的實例類型。
除了使用類型形參作為類型以外,泛型類聲明中的成員與非泛型類的成員遵循相同的規則。下面幾小節將討論適用于特定種類的成員的附加規則。
.Net泛型類中的靜態字段
泛型類聲明中的靜態變量在相同封閉構造類型的所有實例之間共享,但是不會在不同封閉構造類型的實例之間共享。不管靜態變量的類型是否涉及任何類型形參,這些規則都適用。
例如:
- class C﹤V﹥
- {
- static int count = 0;
- public C() {
- count++;
- }
- public static int Count {
- get { return count; }
- }
- }
- class Application
- {
- static void Main() {
- C﹤int﹥ x1 = new C﹤int﹥();
- Console.WriteLine(C﹤int﹥.Count); // Prints 1
- C﹤double﹥ x2 = new C﹤double﹥();
- Console.WriteLine(C﹤int﹥.Count); // Prints 1
- C﹤int﹥ x3 = new C﹤int﹥();
- Console.WriteLine(C﹤int﹥.Count); // Prints 2
- }
- }
.Net泛型類中的靜態構造函數
泛型類中的靜態構造函數用于初始化靜態字段,并為從該泛型類聲明創建的每個不同封閉構造類型執行其他初始化操作。泛型類型聲明的類型形參處于作用域中,并且可在靜態構造函數的函數體中使用。
新的封閉構造類類型在***次發生下列任一情況時進行初始化:
·創建該封閉構造類型的實例。
·引用該封閉構造類型的任何靜態成員。
為了初始化新的封閉構造類類型,需要先為該特定的封閉構造類型創建一組新的靜態字段。將其中的每個靜態字段初始化為默認值。下一步,為這些靜態字段執行靜態字段初始值設定項。***,執行靜態構造函數。
由于靜態構造函數只為每個封閉構造類類型執行一次,因此對于無法通過約束在編譯時進行檢查的類型形參來說,此處是進行運行時檢查的方便位置。例如,下面的類型使用靜態構造函數檢查類型實參是否為一個枚舉:
- class Gen﹤T﹥ where T: struct
- {
- static Gen() {
- if (!typeof(T).IsEnum) {
- throw new ArgumentException("T must be an enum");
- }
- }
- }
.Net泛型類之訪問受保護成員
在泛型類聲明中,通過從該泛型類構造的任何類類型的實例,可以對繼承的受保護實例成員進行訪問。具體而言,指定的用于訪問 protected 和 protected internal 實例成員的規則通過下面針對泛型的規則進行了擴充:
在泛型類 G 中,如果 E 的類型是從 G 構造的類類型或從 G 構造的類類型繼承的類類型,則使用 E.M 形式的 primary-expression 訪問繼承的受保護實例成員 M 是允許的。
在下面的示例中
- class C﹤T﹥
- {
- protected T x;
- }
- class D﹤T﹥: C﹤T﹥
- {
- static void F() {
- D﹤T﹥ dt = new D﹤T﹥();
- D﹤int﹥ di = new D﹤int﹥();
- D﹤string﹥ ds = new D﹤string﹥();
- dt.x = default(T);
- di.x = 123;
- ds.x = "test";
- }
- }
對 x 的三個賦值是允許的,因為它們全都通過從該泛型類型構造的類類型的實例進行。
.Net泛型類中的重載
泛型類聲明中的方法、構造函數、索引器和運算符可以被重載。雖然聲明的簽名必須唯一,但是在替換類型實參時可能會導致出現完全相同的簽名。在這樣的情況下,重載解析的附加規則將挑選最明確的
成員。
下面的示例根據此規則演示有效和無效的重載:
- interface I1﹤T﹥ {}
- interface I2﹤T﹥ {}
- class G1﹤U﹥
- {
- int F1(U u); // Overload resulotion for G﹤int﹥.F1
- int F1(int i); // will pick non-generic
- void F2(I1﹤U﹥ a); // Valid overload
- void F2(I2﹤U﹥ a);
- }
- class G2﹤U,V﹥
- {
- void F3(U u, V v);// Valid, but overload resolution for
- void F3(V v, U u);// G2﹤int,int﹥.F3 will fail
- void F4(U u, I1﹤V﹥ v); // Valid, but overload resolution for
- void F4(I1﹤V﹥ v, U u);// G2﹤I1﹤int﹥,int﹥.F4 will fail
- void F5(U u1, I1﹤V﹥ v2); // Valid overload
- void F5(V v1, U u2);
- void F6(ref U u); // valid overload
- void F6(out V v);
- }
形參數組方法和類型形參
可以在形參數組的類型中使用類型形參。例如,給定下面的聲明
- class C﹤V﹥
- {
- static void F(int x, int y, params V[] args);
- }
對該方法的如下展開形式的調用:
- C﹤int﹥.F(10, 20);
- C﹤object﹥.F(10, 20, 30, 40);
- C﹤string﹥.F(10, 20, "hello", "goodbye");
完全對應于:
- C﹤int﹥.F(10, 20, new int[] {});
- C﹤object﹥.F(10, 20, new object[] {30, 40});
- C﹤string﹥.F(10, 20, new string[] {"hello", "goodbye"} );
.Net泛型類之重寫和泛型類
和往常一樣,泛型類中的函數成員可以重寫基類中的函數成員。在確定被重寫的基成員時,必須通過替換類型實參來確定基類的成員。一旦確定了基類的成員,重寫規則就與非泛型類
相同。
下面的示例演示重寫規則如何在存在泛型的情況下起作用:
- abstract class C﹤T﹥
- {
- public virtual T F() {}
- public virtual C﹤T﹥ G() {}
- public virtual void H(C﹤T﹥ x) {}
- }
- class D: C﹤string﹥
- {
- public override string F() {} // Ok
- public override C﹤string﹥ G() {} // Ok
- public override void H(C﹤T﹥ x) {} // Error, should be C﹤string﹥
- }
- class E﹤T,U﹥: C﹤U﹥
- {
- public override U F() {}// Ok
- public override C﹤U﹥ G() {} // Ok
- public override void H(C﹤T﹥ x) {} // Error, should be C﹤U﹥
- }
.Net泛型類中的運算符
泛型類聲明可以定義運算符,所遵循的規則與非泛型類聲明相同。運算符聲明中使用類聲明的實例類型的方式必須與運算符的正常使用規則類似,具體如下:
·一元運算符必須以該實例類型的單個參數為操作對象。一元的 ++ 和 -- 運算符必須返回該實例類型或從該實例類型派生的類型。
·二元運算符的參數中必須至少有一個屬于該實例類型。
·轉換運算符的形參類型或返回類型必須屬于該實例類型。
下面演示泛型類中的有效運算符聲明的一些示例:
- class X﹤T﹥
- {
- public static X﹤T﹥ operator ++(X﹤T﹥ operand) {}
- public static int operator *(X﹤T﹥ op1, int op2) {}
- public static explicit operator X﹤T﹥(T value) {}
- }
對于從源類型 S 轉換到目標類型 T 的轉換運算符,在應用指定的規則時,與 S 或 T 關聯的任何類型形參都被視為與其他類型沒有繼承關系的唯一類型,并忽略對那些類型形參的所有約束。
在下面的示例中
- class C﹤T﹥ {}
- class D﹤T﹥: C﹤T﹥
- {
- public static implicit operator C﹤int﹥(D﹤T﹥ value) {} // Ok
- public static implicit operator C﹤string﹥(D﹤T﹥ value) {} // Ok
- public static implicit operator C﹤T﹥(D﹤T﹥ value) {} // Error
- }
前兩個運算符聲明是允許的,T 和 int 以及 string 分別被視為沒有關系的唯一類型。但是,第三個運算符是錯誤的,因為 C﹤T﹥ 是 D﹤T﹥ 的基類。
對于某些類型實參,可以聲明這樣的運算符,即這些運算符指定了已經作為預定義轉換而存在的轉換。在下面的示例中
- struct Convertible﹤T﹥
- {
- public static implicit operator Convertible﹤T﹥(T value) {}
- public static explicit operator T(Convertible﹤T﹥ value) {}
- }
當把類型 object 指定為 T 的類型實參時,第二個運算符將聲明一個已經存在的轉換(存在從任何類型到類型 object 的隱式轉換,因此也存在顯式轉換)。
在兩個類型之間存在預定義轉換的情況下,這些類型之間的任何用戶定義的轉換將被忽略。具體而言:
·如果存在從類型 S 到類型T 的預定義隱式轉換,則從S 到T 的所有用戶定義的轉換(隱式或顯式)將被忽略。
·如果存在從類型S 到類型T 的預定義顯式轉換,則從 S 到T 的所有用戶定義的顯式轉換將被忽略。但是,仍然會考慮從 S 到 T 的用戶定義的隱式轉換。
對于除 object 以外的所有類型,上面的 Convertible﹤T﹥ 類型聲明的運算符都不會與預定義的轉換發生沖突。例如:
- void F(int i, Convertible﹤int﹥ n) {
- i = n; // Error
- i = (int)n; // User-defined explicit conversion
- n = i; // User-defined implicit conversion
- n = (Convertible﹤int﹥)i; // User-defined implicit conversion
- }
但是對于類型 object,除了下面這個特例之外,預定義的轉換將在其他所有情況下隱藏用戶定義的
轉換:
- void F(object o, Convertible﹤object﹥ n) {
- o = n; // Pre-defined boxing conversion
- o = (object)n; // Pre-defined boxing conversion
- n = o; // User-defined implicit conversion
- n = (Convertible﹤object﹥)o; // Pre-defined unboxing conversion
- }
.Net泛型類中的嵌套類型
泛型類聲明可以包含嵌套的類型聲明。包容類的類型形參可以在嵌套類型中使用。嵌套類型聲明可以包含僅適用于該嵌套類型的附加類型形參。
泛型類聲明中包含的每個類型聲明都隱式地是泛型類型聲明。在編寫對嵌套在泛型類型中的類型的引用時,必須指定其包容構造類型(包括其類型實參)。但是可在外層類中不加限定地使用嵌套類型;在構造嵌套類型時可以隱式地使用外層類的實例類型。下面的示例演示三種不同的引用從 Inner 創建的構造類型的正確方法;前兩種方法是等效的:
- class Outer﹤T﹥
- {
- class Inner﹤U﹥
- {
- public static void F(T t, U u) {}
- }
- static void F(T t) {
- Outer﹤T﹥.Inner﹤string﹥.F(t, "abc"); // These two statements have
- Inner﹤string﹥.F(t, "abc"); // the same effect
- Outer﹤int﹥.Inner﹤string﹥.F(3, "abc"); // This type is different
- Outer.Inner﹤string﹥.F(t, "abc");// Error, Outer needs type arg
- }
- }
嵌套類型中的類型形參可以隱藏外層類型中聲明的成員或類型形參,但這是一種不好的編程風格:
- class Outer﹤T﹥
- {
- class Inner﹤T﹥ // Valid, hides Outer’s T
- {
- public T t; // Refers to Inner’s T
- }
- }
.Net泛型類的聲明以及使用中會碰到的一些概念及應用的基本內容就向你介紹到這里,希望對你了解和學習.Net泛型類有所幫助。
【編輯推薦】