Maison >développement back-end >Tutoriel C#.Net >Spécification C# 2.0 (Génériques 5)

Spécification C# 2.0 (Génériques 5)

黄舟
黄舟original
2017-01-03 10:39:051325parcourir

Connecté au quatre générique

20.6.5 Ambiguïté syntaxique

Dans §20.9.3 et §20.9.4 nom simple et accès aux membres Il est facile de provoquer une ambiguïté grammaticale pour les expressions . Par exemple, l'instruction

F(G<A,B>(7));


peut être interprétée comme un appel à F avec deux paramètres G8290428646c1d9cd976d5793032dd7ef(7) [1]. De même, cela peut être interprété comme un appel à F avec un argument, qui est un appel à une méthode générique G avec deux arguments de type et un argument formel.
Si l'expression peut être analysée en deux méthodes valides différentes, alors ">" peut être analysé comme tout ou partie d'un opérateur, ou comme une liste d'arguments de type, puis suivi de ">" Les balises suivantes seront vérifiées . S'il s'agit de l'un des éléments suivants :

{ } ] > : ; , . ?


alors ">" est analysé comme une liste d'arguments de type. Sinon, ">" est analysé comme un opérateur.

20.6.6 Utiliser des méthodes génériques avec des délégués

Des instances d'un délégué peuvent être créées en faisant référence à une déclaration d'une méthode générique. Le traitement exact au moment de la compilation des expressions déléguées, y compris les expressions de création de délégués qui font référence à des méthodes génériques, est décrit au §20.9.6.
Lors de l'appel d'une méthode générique via un délégué, les arguments de type utilisés seront déterminés lors de l'instanciation du délégué. Les arguments de type peuvent être donnés explicitement via une liste d'arguments de type, ou déterminés via l'inférence de type (§20.6.4). Si l'inférence de type est utilisée, le type de paramètre du délégué sera utilisé comme type d'argument pour le processus d'inférence. Le type de retour du délégué n'est pas utilisé pour l'inférence. L'exemple suivant montre comment fournir des arguments de type pour une expression d'instanciation de délégué.

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); //错误,不能从返回类型推断
}
}

Dans l'exemple précédent, le type délégué non générique a été instancié à l'aide d'une méthode générique. Vous pouvez également utiliser des méthodes génériques pour créer une instance d'un type de délégué construit. Dans tous les cas, les arguments de type sont donnés ou peuvent être déduits lors de la création de l'instance du délégué, mais le délégué est appelé sans fournir de liste d'arguments de type (§15.3).

20.6.7 Propriétés, événements, indexeurs ou opérateurs non génériques

Les propriétés, événements, indexeurs et opérateurs eux-mêmes ne peuvent pas avoir d'arguments de type (bien qu'ils puissent se produire dans une classe générique , et les arguments de type sont disponibles à partir d'une classe englobante). Si vous avez besoin d’une construction générique comme une propriété, vous devez plutôt utiliser une méthode générique.

20.7 Contraintes

Les déclarations génériques de type et de méthode peuvent éventuellement spécifier des contraintes de paramètre de type. Cela peut être fait en incluant une instruction de contrainte de paramètre de type dans la déclaration.

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上指定约束,以满足由基类B8742468051c85b06f0a0af9e3e506b5c所施加的约束。相反,类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的直接显式转换是允许的,你可能很容易以为Xbd43222e33876353aff11e13a7dc75f6.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)!


Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn