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

接泛型四

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
C#.NET生態系統:框架,庫和工具C#.NET生態系統:框架,庫和工具Apr 24, 2025 am 12:02 AM

C#.NET生態系統提供了豐富的框架和庫,幫助開發者高效構建應用。 1.ASP.NETCore用於構建高性能Web應用,2.EntityFrameworkCore用於數據庫操作。通過理解這些工具的使用和最佳實踐,開發者可以提高應用的質量和性能。

將C#.NET應用程序部署到Azure/AWS:逐步指南將C#.NET應用程序部署到Azure/AWS:逐步指南Apr 23, 2025 am 12:06 AM

如何將C#.NET應用部署到Azure或AWS?答案是使用AzureAppService和AWSElasticBeanstalk。 1.在Azure上,使用AzureAppService和AzurePipelines自動化部署。 2.在AWS上,使用AmazonElasticBeanstalk和AWSLambda實現部署和無服務器計算。

C#.NET:強大的編程語言簡介C#.NET:強大的編程語言簡介Apr 22, 2025 am 12:04 AM

C#和.NET的結合為開發者提供了強大的編程環境。 1)C#支持多態性和異步編程,2).NET提供跨平台能力和並發處理機制,這使得它們在桌面、Web和移動應用開發中廣泛應用。

.NET框架與C#:解碼術語.NET框架與C#:解碼術語Apr 21, 2025 am 12:05 AM

.NETFramework是一個軟件框架,C#是一種編程語言。 1..NETFramework提供庫和服務,支持桌面、Web和移動應用開發。 2.C#設計用於.NETFramework,支持現代編程功能。 3..NETFramework通過CLR管理代碼執行,C#代碼編譯成IL後由CLR運行。 4.使用.NETFramework可快速開發應用,C#提供如LINQ的高級功能。 5.常見錯誤包括類型轉換和異步編程死鎖,調試需用VisualStudio工具。

揭開c#.net的神秘面紗:初學者的概述揭開c#.net的神秘面紗:初學者的概述Apr 20, 2025 am 12:11 AM

C#是一種由微軟開發的現代、面向對象的編程語言,.NET是微軟提供的開發框架。 C#結合了C 的性能和Java的簡潔性,適用於構建各種應用程序。 .NET框架支持多種語言,提供垃圾回收機制,簡化內存管理。

C#和.NET運行時:它們如何一起工作C#和.NET運行時:它們如何一起工作Apr 19, 2025 am 12:04 AM

C#和.NET運行時緊密合作,賦予開發者高效、強大且跨平台的開發能力。 1)C#是一種類型安全且面向對象的編程語言,旨在與.NET框架無縫集成。 2).NET運行時管理C#代碼的執行,提供垃圾回收、類型安全等服務,確保高效和跨平台運行。

C#.NET開發:入門的初學者指南C#.NET開發:入門的初學者指南Apr 18, 2025 am 12:17 AM

要開始C#.NET開發,你需要:1.了解C#的基礎知識和.NET框架的核心概念;2.掌握變量、數據類型、控制結構、函數和類的基本概念;3.學習C#的高級特性,如LINQ和異步編程;4.熟悉常見錯誤的調試技巧和性能優化方法。通過這些步驟,你可以逐步深入C#.NET的世界,並編寫高效的應用程序。

c#和.net:了解兩者之間的關係c#和.net:了解兩者之間的關係Apr 17, 2025 am 12:07 AM

C#和.NET的關係是密不可分的,但它們不是一回事。 C#是一門編程語言,而.NET是一個開發平台。 C#用於編寫代碼,編譯成.NET的中間語言(IL),由.NET運行時(CLR)執行。

See all articles

熱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

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

熱工具

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

記事本++7.3.1

記事本++7.3.1

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

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),