接(泛型一)
這篇文章是翻譯的微軟的技術文章.供學習c#的朋友參考,請勿用於商業目的。http://msdn.microsoft.com/vcsharp/team/language/default.aspx
20.1.6泛型類中的靜態構造函數
在泛型類中的靜態構造函數被用於初始化靜態字段,爲每個從特定泛型類聲明中創建的不同的封閉構造類型,執行其他初始化。泛型類型聲明的類型參數在作用域之內,可以在靜態構造函數體內被使用。
如果下列情形之一發生,一個新的封閉構造類類型將被首次初始化。
- 一個封閉構造類型的實例被創建時<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
- 封閉構造類型的任何靜態成員被引用時
爲了初始化一個新的封閉的構造類類型,首先那個特定封閉類型的一組新靜態字段(§20.1.5)將會被創建。每個靜態字段都被初始化爲其默認值(§5.2)。接着,靜態字段初始化器(§10.4.5.1)將爲這些靜態字段執行。最後靜態構造函數將被執行。
由於靜態構造函數將爲每個封閉構造類類型執行一次,那麼在不能通過約束(§20.7)檢查的類型參數上實施運行時檢查,將會很方便。例如,下面的類型使用一個靜態構造函數檢查一個類型參數是否是一個引用類型。
class Gen<T>
{
static Gen(){
if((object)T.default != null){
throw new ArgumentException(“T must be a reference type”);
}
}
}
20.1.7 訪問受保護的成員
在一個泛型類聲明中,對於繼承的受保護的實例成員的訪問是允許的,通過從泛型類構造的任何類型的實例就可以做到。尤其是,用於訪問§3.5.3中指定的protected和protected internal實例成員的規則,對於泛型使用如下的規則進行了擴充。
- 在一個泛型類G中,對於一個繼承的受保護的實例成員M,使用E.M的基本表達式是允許的,前提是E的類型是一個從G構造的類類型,或繼承於一個從G構造的類類型的類類型。
在例子
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 = T.default;
di.x = 123;
ds.x = “test”;
}
}
三個對x的賦值語句都是允許的,因爲它們都通過從泛型構造的類類型的實例發生。
20.1.8在泛型類中重載
在一個泛型類聲明中的方法、構造函數、索引器和運算符可以被重載。但爲了避免在構造類中的歧義,這些重載是受約束的。在同一個泛型類聲明中使用相同的名字聲明的兩個函數成員必須具有這樣的參數類型,也就是封閉構造類型中不能出現兩個成員使用相同的名字和簽名。當考慮所有可能的封閉構造類型時,這條規則包含了在當前程序中目前不存在的類型是實參,但它仍然是可能出現的[1]。在類型參數上的類型約束由於這條規則的目的而被忽略了。
下面的例子根據這條規則展示了有效和無效的重載。
nterface I1<T> {…}
interface I2<T>{…}
class G1<U>
{
long F1(U u); //無效重載,G<int>將會有使用相同簽名的兩個成員
int F1(int i);
void F2(U u1, U u2); //有效重載,對於U沒有類型參數
void F2(int I , string s); //可能同時是int和string
void F3(I1<U>a); //有效重載
void F3(I2<U>a);
void F4(U a); //有效重載
void F4(U[] a);}
class G2<U,V>
{
void F5(U u , V v); //無效重載,G2<int , int>將會有兩個簽名相同的成員
void F5(V v, U u);
void F6(U u , I1<V> v);//無效重載,G2<I1<int>,int>將會有兩個簽名相同的成員
void F6(I1<V> v , U u);
void F7(U u1,I1<V> V2);//有效的重載,U不可能同時是V和I1<V>
void F7(V v1 , U u2);
void F8(ref U u); //無效重載
void F8(out V v);
}
class C1{…}
class C2{…}
class G3<U , V> where U:C1 where V:C2
{
void F9(U u); //無效重載,當檢查重載時,在U和V上的約束將被忽略
void F9(V v);
}
20.1.9參數數組方法和類型參數
類型參數可以被用在參數數組的類型中。例如,給定聲明
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”));
20.1.10重寫和泛型類
在泛型類中的函數成員可以重寫基類中的函數成員。如果基類是一個非泛型類型或封閉構造類型,那麼任何重寫函數成員不能有包含類型參數的組成類型。然而,如果一個基類是一個開放構造類型,那麼重寫函數成員可以使用在其聲明中的類型參數。當重寫基類成員時,基類成員必須通過替換類型實參而被確定,如§20.5.4中所描述的。一旦基類的成員被確定,用於重寫的規則和非泛型類是一樣的。
下面的例子演示了對於現有的泛型其重寫規則是如何工作的。
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); //錯誤,應該是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){…}//錯誤,應該是C<U>
}
20.1.11泛型類中的運算符
泛型類聲明可以定義運算符,它遵循和常規類相同的規則。類聲明的實例類型(§20.1.2)必須以一種類似於運算符的常規規則的方式,在運算符聲明中被使用,如下
- 一元運算符必須接受一個實例類型的單一參數。一元運算符“++”和“—”必須返回實例類型。
- 至少二元運算符的參數之一必須是實例類型。
- 轉換運算符的參數類型和返回類型都必須是實例類型。
下面展示了在泛型類中幾個有效的運算符聲明的例子
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的轉換運算符,當應用§10.9.3中的規則時,任何關聯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<T>(D<T> value){…}//錯誤
}
第一個運算符聲明是允許的,由於§10.9.3的原因,T和int被認爲是沒有關係的唯一類型。然而,第二個運算符是一個錯誤,因爲C<T>是D<T>的基類。
給定先前的例子,爲某些類型實參聲明運算符,指定已經作爲預定義轉換而存在的轉換是可能的。
struct Nullable<T>
{
public static implicit operator Nullable<T>(T value){…}
public static explicit operator T(Nullable<T> value){…}
}
當類型object作爲T的類型實參被指定,第二個運算符聲明瞭一個已經存在的轉換(從任何類型到object是一個隱式的,也可以是顯式的轉換)。
在兩個類型之間存在預定義的轉換的情形下,在這些類型上的任何用戶定義的轉換都將被忽略。尤其是
- 如果存在從類型S到類型T的預定義的隱式轉換(§6.1),所有用戶定義的轉換(隱式的或顯式的)都將被忽略。
- 如果存在從類型S到類型T的預定義的顯式轉換,那麼任何用戶定義的從類型S到類型T的顯式轉換都將被忽略。但用戶定義的從S到T的隱式轉換仍會被考慮。
對於所有類型除了object,由Nullable<T>類型聲明的運算符都不會與預定義的轉換衝突。例如
void F(int I , Nullable<int> n){
i = n; //錯誤
i = (int)n; //用戶定義的顯式轉換
n = i; //用戶定義的隱式轉換
n = (Nullable<int>)i; //用戶定義的隱式轉換
}
然而,對於類型object,預定義的轉換在所有情況隱藏了用戶定義轉換,除了一種情況:
void F(object o , Nullable<object> n){
o = n; //預定義裝箱轉換
o= (object)n; //預定義裝箱轉換
n= o; //用戶定義隱式轉換
n = (Nullable<object>)o; //預定義取消裝箱轉換
}
20.1.12泛型類中的嵌套類型
泛型類聲明可以包含嵌套類型聲明。封閉類的類型參數可以在嵌套類型中使用。嵌套類型聲明可以包含附加的類型參數,它只適用於該嵌套類型。
包含在泛型類聲明中的每個類型聲明是隱式的泛型類型聲明。當編寫一個嵌套在泛型類型內的類型的引用時,包含構造類型,包括它的類型實參,必須被命名。然而,在外部類中,內部類型可以被無限制的使用;當構造一個內部類型時,外部類的實例類型可以被隱式地使用。下面的例子展示了三個不同的引用從Inner創建的構造類型的方法,它們都是正確的;前兩個是等價的。
class Outer<T>
{
class Inner<U>
{
static void F(T t , U u){…}
}
static void F(T t)
{
Outer<T>.Inner<string >.F(t,”abc”);//這兩個語句有同樣的效果
Inner<string>.F(t,”abc”);
Outer<int>.Inner<string>.F(3,”abc”); //這個類型是不同的
Outer.Inner<string>.F(t , “abc”); //錯誤,Outer需要類型參數
}
}
儘管這是一種不好的編程風格,但嵌套類型中的類型參數可以隱藏一個成員,或在外部類型中聲明的一個類型參數。
class Outer<T>
{
class Inner<T> //有效,隱藏了 Ouer的 T
{
public T t; //引用Inner的T
}
}
20.1.13應用程序入口點
應用程序入口點不能在一個泛型類聲明中。
20.2泛型結構聲明
像類聲明一樣,結構聲明可以有可選的類型參數。
struct-declaration:(結構聲明:)
attributes opt struct-modifiers opt struct identifier type-parameter-list opt struct-interfaces opt type-parameter-constraints-clauses opt struct-body ;opt
(特性可選 結構修飾符可選struct 標識符 類型參數列表可選結構接口可選類型參數約束語句可選 結構體;可選)
除了§11.3中爲結構聲明而指出的差別之外,泛型類聲明的規則也適用於泛型結構聲明。
20.3泛型接口聲明
接口也可以定義可選的類型參數
interface-declaration:(接口聲明:)
attribute opt interface-modifiers opt interface indentifier type-parameter-list opt
interface-base opt type-parameter-constraints-clause opt interface-body;
(特性可選 接口修飾符可選 interface 標識符 類型參數列表可選 基接口可選類型參數約束語句可選 接口體;可選)
使用類型參數聲明的接口是一個泛型接口聲明。除了所指出的那些,泛型接口聲明遵循和常規結構聲明相同的規則。
在接口聲明中的每個類型參數在接口的聲明空間定義了一個名字。在一個接口上的類型參數的作用域包括基接口、類型約束語句和接口體。在其作用域之內,一個類型參數可以被用作一個類型。應用到接口上的類型參數和應用到類(§20.1.1)上的類型參數具有相同的限制。
在泛型接口中的方法與泛型類(§20.1.8)中的方法遵循相同的重載規則。
20.3.1實現接口的唯一性
由泛型類型聲明實現的接口必須爲所有可能的構造類型保留唯一性。沒有這條規則,將不可能爲特定的構造類型確定調用正確的方法。例如,假定一個泛型類聲明允許如下寫法。
interface I<T>
{
void F();
}
class X<U, V>:I<U>,I<V> //錯誤,I<U>和I<V>衝突
{
void I<U>.F(){…}
void I<V>.F(){…}
}
如果允許這麼寫,那麼下面的情形將無法確定執行那段代碼。
I<int> x = new X<int ,int>();
x.F();
爲了確定一個泛型類型聲明的接口列表是有效的,可以按下面的步驟進行。
- 讓L成爲在泛型類、結構或接口聲明 C中指定的接口的列表。
- 將任何已經在L中的接口的基接口添加到L
- 從L中刪除任何重複的接口
- 在類型實參被替換到L後,如果任何從C創建的可能構造類型,導致在L中的兩個接口是同一的,那麼C的聲明是無效的。當確定所有可能的構造類型時,約束聲明不予考慮。
在類聲明X之上,接口列表L由I<U>和I<V>組成。該聲明是無效的,因爲任何使用相同類型U和V的構造類型,將導致這兩個接口是同一的。
20.3.2顯式接口成員實現
使用構造接口類型的顯式接口成員實現本質上與簡單接口類型方式上是相同的。和以往一樣,顯式接口成員實現必須由一個指明哪個接口被實現的接口類型而限定。該類型可能是一個簡單接口或構造接口,如下例子所示。
interface IList<T>
{
T[] GetElement();
}
interface IDictionary<K,V>
{
V this[K key];
Void Add(K key , V value);
}
class List<T>:IList<T>,IDictionary<int , T>
{
T[] IList<T>.GetElement(){…}
T IDictionary<int , T>.this[int index]{…}
void IDictionary<int , T>.Add(int index , T value){…}
}
[1] 也就是在類型參數被替換成類型實參時,有可能替換後的實參導致出現兩個成員使用相同的名字和簽名。