首頁 後端開發 C#.Net教程 C# 2.0 Specification(泛型五)

C# 2.0 Specification(泛型五)

Jan 03, 2017 am 10:39 AM

接泛型四

20.6.5語法歧義

在§20.9.3和§20.9.4中簡單名字(simple-name)和成員存取(member-access)對於表達式來說容易引起語法歧義。例如,語句

F(G<A,B>(7));
登入後複製


可以解釋為對帶有兩個參數G(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所施加的约束。相反,类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.F(7)将返回7L。但实际不是,因为标准的数值转换只有在类型在编译时是已知的时候才被考虑。为了使语义更清楚,先前的例子必须按如下形式编写。

class X<T>
{
public static long F(T t)
{
return (long)(object)t; //ok;允许转换
}
}
登入後複製

[1] 这种情况下“>”被解释为大于运算符。

[2] 在程序中重写ToString时,一般不推荐添加这种类似的计算逻辑,因为它的这种结果变化不易控制,增加了调试程序的复杂性。

[3] C的基类或其基类的基类等。

以上就是C# 2.0 Specification(泛型五)的内容,更多相关内容请关注PHP中文网(www.php.cn)!


本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

C語言各種符號的使用方法 C語言各種符號的使用方法 Apr 03, 2025 pm 04:48 PM

C 語言中符號的使用方法涵蓋算術、賦值、條件、邏輯、位運算符等。算術運算符用於基本數學運算,賦值運算符用於賦值和加減乘除賦值,條件運算符用於根據條件執行不同操作,邏輯運算符用於邏輯操作,位運算符用於位級操作,特殊常量用於表示空指針、文件結束標記和非數字值。

char在C語言字符串中的作用是什麼 char在C語言字符串中的作用是什麼 Apr 03, 2025 pm 03:15 PM

在 C 語言中,char 類型在字符串中用於:1. 存儲單個字符;2. 使用數組表示字符串並以 null 終止符結束;3. 通過字符串操作函數進行操作;4. 從鍵盤讀取或輸出字符串。

char在C語言中如何處理特殊字符 char在C語言中如何處理特殊字符 Apr 03, 2025 pm 03:18 PM

C語言中通過轉義序列處理特殊字符,如:\n表示換行符。 \t表示製表符。使用轉義序列或字符常量表示特殊字符,如char c = '\n'。注意,反斜杠需要轉義兩次。不同平台和編譯器可能有不同的轉義序列,請查閱文檔。

c#多線程和異步的區別 c#多線程和異步的區別 Apr 03, 2025 pm 02:57 PM

多線程和異步的區別在於,多線程同時執行多個線程,而異步在不阻塞當前線程的情況下執行操作。多線程用於計算密集型任務,而異步用於用戶交互操作。多線程的優勢是提高計算性能,異步的優勢是不阻塞 UI 線程。選擇多線程還是異步取決於任務性質:計算密集型任務使用多線程,與外部資源交互且需要保持 UI 響應的任務使用異步。

char與wchar_t在C語言中的區別 char與wchar_t在C語言中的區別 Apr 03, 2025 pm 03:09 PM

在 C 語言中,char 和 wchar_t 的主要區別在於字符編碼:char 使用 ASCII 或擴展 ASCII,wchar_t 使用 Unicode;char 佔用 1-2 個字節,wchar_t 佔用 2-4 個字節;char 適用於英語文本,wchar_t 適用於多語言文本;char 廣泛支持,wchar_t 依賴於編譯器和操作系統是否支持 Unicode;char 的字符範圍受限,wchar_t 的字符範圍更大,並使用專門的函數進行算術運算。

char在C語言中如何進行類型轉換 char在C語言中如何進行類型轉換 Apr 03, 2025 pm 03:21 PM

在 C 語言中,char 類型轉換可以通過:強制類型轉換:使用強制類型轉換符將一種類型的數據直接轉換為另一種類型。自動類型轉換:當一種類型的數據可以容納另一種類型的值時,編譯器自動進行轉換。

char和unsigned char的區別是什麼 char和unsigned char的區別是什麼 Apr 03, 2025 pm 03:36 PM

char 和 unsigned char 是存儲字符數據的兩種數據類型,主要區別在於處理負數和正數的方式:值範圍:char 有符號 (-128 到 127),unsigned char 無符號 (0 到 255)。負數處理:char 可以存儲負數,unsigned char 不能。位模式:char 最高位表示符號,unsigned char 無符號位。算術運算:char 和 unsigned char 作為有符號和無符號類型,其算術運算方式不同。兼容性:char 和 unsigned char

char數組在C語言中如何使用 char數組在C語言中如何使用 Apr 03, 2025 pm 03:24 PM

char 數組在 C 語言中存儲字符序列,聲明為 char array_name[size]。訪問元素通過下標運算符,元素以空終止符 '\0' 結尾,用於表示字符串終點。 C 語言提供多種字符串操作函數,如 strlen()、strcpy()、strcat() 和 strcmp()。

See all articles