程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C# 2.0 Specification(泛型五)

C# 2.0 Specification(泛型五)

編輯:關於C語言
接泛型四
20.6.5語法歧義
在§20.9.3和§20.9.4中簡單名字(simple-name)和成員訪問(member-Access)對於表達式來說容易引起語法歧義。例如,語句
F(G<A,B>(7));



可以被解釋為對帶有兩個參數G<A和B>(7)的F的調用[1]。同樣,它還能被解釋為對帶有一個參數的F的調用,這是一個對帶有兩個類型實參和一個正式參數的泛型方法G的調用。
如果表達式可以被解析為兩種不同的有效方法,那麼在“>”能被解析作為運算符的所有或一部分時,或者作為一個類型實參列表,那麼緊隨“>”之後的標記將會被檢查。如果它是如下之一:
{ } ] > : ; , . ?
那麼“>”被解析作為類型實參列表。否則“>”被解析作為一個運算符。
20.6.6對委托使用泛型方法
委托的實例可通過引用一個泛型方法的聲明而創建。委托表達式確切的編譯時處理,包括引用泛型方法的委托創建表達式,這在§20.9.6中進行了描述。
當通過委托調用一個泛型方法時,所使用的類型實參將在委托實例化時被確定。類型實參可以通過類型實參列表顯式給定,或者通過類型推斷(§20.6.4)而確定。如果采用類型推斷,委托的參數類型將被用作推斷處理過程的實參類型。委托的返回類型不用於推斷。下面的例子展示了為一個委托實例化表達式提供類型實參的方法。
delegate int D(string s , int i)
delegate int E();
class X
{
public static T F<T>(string s ,T t){…}
public static T G<T>(){…}
static void Main()
{
D d1 = new D(F<int>); //ok,類型實參被顯式給定
D d2 = new D(F); //ok,int作為類型實參而被推斷
E e1 = new E(G<int>); //ok,類型實參被顯式給定
E e2 = new E(G); //錯誤,不能從返回類型推斷
}
}
在先前的例子中,非泛型委托類型使用泛型方法實例化。你也可以使用泛型方法創建一個構造委托類型的實例。在所有情形下,當委托實例被創建時,類型實參被給定或可以被推斷,但委托被調用時,可以不用提供類型實參列表(§15.3)。


20.6.7非泛型屬性、事件、索引器或運算符
屬性、事件、索引器和運算符他們自身可以沒有類型實參(盡管他們可以出現在泛型類中,並且可從一個封閉類中使用類型實參)。如果需要一個類似屬性泛型的構件,取而代之的是你必須使用一個泛型方法。
20.7約束
泛型類型和方法聲明可以可選的指定類型參數約束,這通過在聲明中包含類型參數約束語句就可以做到。
type-parameter-constraints-clauses(類型參數約束語句:)
type-parameter-constraints-clause(類型參數約束語句)
type-parameter-constraints-clauses type-parameter-constraints-clause(類型參數約束語句 類型參數約束語句)
type-parameter-constraints-clause:(類型參數約束語句:)
where type-parameter : type-parameter-constraints(where 類型參數:類型參數約束)
type-parameter-constraints:(類型參數約束:)
class-constraint(類約束)
interface-constraints(接口約束)
constructor-constraint(構造函數約束)
class-constraint , interface-constraints(類約束,接口約束)
class-constraint , constructor-constraint(類約束,構造函數約束)
interface-constraints , constructor-constraint(接口約束,構造函數約束)
class-constraint , interface-constraints , constructor-constraint(類約束,接口約束,構造函數約束)
class-constraint:(類約束:)
class-type(類類型)
interface-constraint:(接口約束:)
interface-constraint(接口約束)
interface-constraints , interface-constraints(接口約束,接口約束)
interface-constraints:(接口約束:)
interface-type(接口類型)
constructor-constraint:(構造函數約束:)
new ( )
每個類型參數約束語句由標志where 緊接著類型參數的名字,緊接著冒號和類型參數的約束列表。對每個類型參數只能有一個where 語句,但where語句可以以任何順序列出。與屬性訪問器中的get和set標志相似,where 語句不是關鍵字。


在where語句中給定的約束列表可以以這個順序包含下列組件:一個單一的類約束、一個或多個接口約和構造函數約束new ()。
如果約束是一個類類型或者接口類型,這個類型指定類型參數必須支持的每個類型實參的最小“基類型”。無論什麼時候使用一個構造類型或者泛型方法,在編譯時對於類型實參的約束建會被檢查。所提供的類型實參必須派生於或者實現那個類型參數個定的所有約束。
被指定作為類約束的類型必須遵循下面的規則。
該類型必須是一個類類型。
該類型必須是密封的(sealed)。
該類型不能是如下的類型:System.Array,System.Delegate,System.Enum,或者System.ValueType類型。
該類型不能是object。由於所有類型派生於object,如果容許的話這種約束將不會有什麼作用。
至多,對於給定類型參數的約束可以是一個類類型。
作為接口約束而被指定的類型必須滿足如下的規則。
該類型必須是一個接口類型。
在一個給定的where語句中相同的類型不能被指定多次。
在很多情況下,約束可以包含任何關聯類型的類型參數或者方法聲明作為構造類型的一部分,並且可以包括被聲明的類型,但約束不能是一個單一的類型參數。
被指定作為類型參數約束的任何類或者接口類型,作為泛型類型或者被聲明的方法,必須至少是可訪問的(§10.5.4)。
如果一個類型參數的where 語句包括new()形式的構造函數約束,則使用new 運算符創建該類型(§20.8.2)的實例是可能的。用於帶有一個構造函數約束的類型參數的任何類型實參必須有一個無參的構造函數(詳細情形參看§20.7)。
下面是可能約束的例子
interface IPrintable
{
void Print();
}


interface IComparable<T>
{
int CompareTo(T value);
}
interface IKeyProvider<T>
{
T GetKey();
}
class Printer<T> where T:IPrintable{…}
class SortedList<T> where T: IComparable<T>{…}
class Dictionary<K,V>
where K:IComparable<K>
where: V: IPrintable,IKeyProvider<K>,new()
{

}

下面的例子是一個錯誤,因為它試圖直接使用一個類型參數作為約束。
class Extend<T , U> where U:T{…}//錯誤
約束的類型參數類型的值可以被用於訪問約束暗示的實例成員。在例子
interface IPrintable
{
void Print();
}
class Printer<T> where T:IPrintable
{
void PrintOne(T x)
{
x.Pint();
}
}
IPrintable的方法可以在x上被直接調用,因為T被約束總是實現IPrintable。

20.7.1遵循約束
無論什麼時候使用構造類型或者引用泛型方法,所提供的類型實參都將針對聲明在泛型類型,或者方法中的類型參數約束作出檢查。對於每個where 語句,對應於命名的類型參數的類型實參A將按如下每個約束作出檢查。



如果約束是一個類類型或者接口類型,讓C表示提供類型實參的約束,該類型實參將替代出現在約束中的任何類型參數。為了遵循約束,類型A必須按如下方式可別轉化為類型C:
- 同一轉換(§6.1.1)
- 隱式引用轉換(§6.1.4)
- 裝箱轉換(§6.1.5)
- 從類型參數A到C(§20.7.4)的隱式轉換。
如果約束是new(),類型實參A不能是abstract並,且必須有一個公有的無參的構造函數。如果如下之一是真實的這將可以得到滿足。
- A是一個值類型(如§4.1.2中所描述的,所有值類型都有一個公有默認構造函數)。
- A是一個非abstract類,並且 A包含一個無參公有構造函數。
- A是一個非abstract類,並且有一個默認構造函數(§10.10.4)。
如果一個或多個類型參數的約束通過給定的類型實參不能滿足,將會出現編譯時錯誤。
因為類型參數不被繼承,同樣約束也決不被繼承。在下面的例子中,D 必須在其類型參數T上指定約束,以滿足由基類B<T>所施加的約束。相反,類E不需要指定約束,因為對於任何T,List<T>實現了IEnumerable接口。
class B<T> where T: IEnumerable{…}
class D<T>:B<T> where T:IEnumerable{…}
class E<T>:B<List<T>>{…}

20.7.2 在類型參數上的成員查找
在由類型參數T給定的類型中,成員查找的結果取決於為T所指定的約束(如果有的話)。如果T沒有約束或者只有new ()約束,在T上的成員查找,像在object上的成員查找一樣,返回一組相同的成員。否則,成員查找的第一個階段,將考慮T所約束的每個類型的所有成員,結果將會被合並,然後隱藏成員將會從合並結果中刪除。


在泛型出現之前,成員查找總是返回在類中唯一聲明的一組成員,或者一組在接口中唯一聲明的成員, 也可能是object類型。在類型參數上的成員查找做出了一些改變。當一個類型參數有一個類約束和一個或多個接口約束時,成員查找可以返回一組成員,這些成員有一些是在類中聲明的,還有一些是在接口中聲明的。下面的附加規則處理了這種情況。
在成員查找過程(§20.9.2)中,在除了object之外的類中聲明的成員隱藏了在接口中聲明的成員。
在方法和索引器的重載決策過程中,如果任何可用成員在一個不同於object的類中聲明,那麼在接口中聲明的所有成員都將從被考慮的成員集合中刪除。

這些規則只有在將一個類約束和接口約束綁定到類型參數上時才有效。通俗的說法是,在一個類約束中定義的成員,對於在接口約束的成員來說總是首選。
20.7.3 類型參數和裝箱
當一個結構類型重寫繼承於System.Object(Equals , GetHashCode或ToString)的虛擬方法,通過結構類型的實例調用虛擬方法將不會導致裝箱。即使當結構被用作一個類型參數,並且調用通過類型參數類型的實例而發生,情況也是如此。例如
using System;
struct Counter
{
int value;
public override string ToString()
{
value++;
return value.ToString();
}
}
class Program
{
static void Test<T>() where T:new()
{
T x = new T();
Console.WriteLine(x.ToString());
Console.WriteLine(x.ToString());
Console.WriteLine(x.ToString());
}
static void Main()
{
Test<Counter>();
}
}


程序的輸出如下
1
2
3
盡管推薦不要讓ToString帶有附加效果(side effect)[2],但這個例子說明了對於三次x.ToString()的調用不會發生裝箱。
當在一個約束的類型參數上訪問一個成員時,裝箱決不會隱式地發生。例如,假定一個接口ICounter包含了一個方法Increment,它可以被用來修改一個值。如果ICounter被用作一個約束,Increment方法的實現將通過Increment在其上調用的變量的引用而被調用,這個變量不是一個裝箱拷貝。
using System;
interface ICounter
{
void Increment();
}
struct Counter:ICounter
{
int value;
public override string ToString()
{
return value.ToString();
}
void ICounter.Increment(){
value++;
}
}

class Program
{
static void Test<T>() where T:new ,ICounter{
T x = new T();
Console.WriteLine(x);
x.Increment(); //修改x`
Console.WriteLine(x);
((ICounter)x).Increment(); //修改x的裝箱拷貝
Console.WriteLine(x);
}
static void Main()
{
Test<Counter>();
}
}


對變量x的首次調用Increment修改了它的值。這與第二次調用Increment是不等價的,第二次修改的是x裝箱後的拷貝,因此程序的輸出如下
20.7.4包含類型參數的轉換
在類型參數T上允許的轉換,取決於為T所指定的約束。所有約束的或非約束的類型參數,都可以有如下轉換。
從T到T的隱式同一轉換。
從T到object 的隱式轉換。在運行時,如果T是一個值類型,這將通過一個裝箱轉換進行。否則,它將作為一個隱式地引用轉換。
從object到T的隱式轉換。在運行時,如果T是一個值類型,這將通過一個取消裝箱操作而進行。否則它將作為一個顯式地引用轉換。
從T到任何接口類型的顯式轉換。在運行時,如果T是一個值類型,這將通過一個裝箱轉換而進行。否則,它將通過一個顯式地引用轉換而進行。
從任何接口類型到T的隱式轉換。在運行時,如果T是一個值類型,這將通過一個取消裝箱操作而進行。否則,它將作為一個顯式引用轉換而進行。

如果類型參數T指定一個接口I作為約束,將存在下面的附加轉換。
從T到I的隱式轉換,以及從T到I的任何基接口類型的轉換。在運行時,如果T是一個值類型,這將作為一個裝箱轉換而進行。否則,它將作為一個隱式地引用轉換而進行。

如果類型參數T指定類型C作為約束,將存在下面的附加轉換:
從T到C的隱式引用轉換,從T到任何C從中派生的類,以及從T到任何從其實現的接口。
從C到T的顯式引用轉換,從C從中派生的類[3]到T,以及C實現的任何接口到T



如果存在從C 到A的隱式用戶定義轉換,從T到 A的隱式用戶定義轉換。
如果存在從A 到C的顯式用戶定義轉換,從A到 T的顯式用戶定義轉換。
從null類型到T的隱式引用轉換
一個帶有元素類型的數組類型T具有object和System.Array之間的相互轉換(§6.1.4,§6.2.3)。如果T有作為約束而指定的類類型,將有如下附加規則
從帶有元素類型T的數組類型AT到帶有元素類型U的數組類型AU的隱式引用轉換,並且如果下列二者成立的話,將存在從AU到AT顯式引用轉換:
- AT和AU 有相同數量的維數。
- U是這些之一:C,C從中派生的類型,C所實現的接口,作為在T上的約束而指定的接口I,或I的基接口。
先前的規則不允許從非約束類型參數到非接口類型的直接隱式轉換,這可能有點奇怪。其原因是為了防止混淆,並且使得這種轉換的語義更明確。例如,考慮下面的聲明。
class X<T>
{
public static long F(T t){
return (long)t; // ok,允許轉換
}
}
如果t到int的直接顯式轉換是允許的,你可能很容易以為X<int>.F(7)將返回7L。但實際不是,因為標准的數值轉換只有在類型在編譯時是已知的時候才被考慮。為了使語義更清楚,先前的例子必須按如下形式編寫。
class X<T>
{
public static long F(T t)
{
return (long)(object)t; //ok;允許轉換
}
}


--------------------------------------------------------------------------------

[1] 這種情況下“>”被解釋為大於運算符。
[2] 在程序中重寫ToString時,一般不推薦添加這種類似的計算邏輯,因為它的這種結果變化不易控制,增加了調試程序的復雜性。
[3] C的基類或其基類的基類等。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved