首页 后端开发 C#.Net教程 C#2.0 Specification(泛型二)

C#2.0 Specification(泛型二)

Jan 03, 2017 am 10:27 AM

20.1.6泛型类中的静态构造函数

在泛型类中的静态构造函数被用于初始化静态字段,为每个从特定泛型类声明中创建的不同的封闭构造类型,执行其他初始化。泛型类型声明的类型参数在作用域之内,可以在静态构造函数体内被使用。

如果下列情形之一发生,一个新的封闭构造类类型将被首次初始化。

一个封闭构造类型的实例被创建时

封闭构造类型的任何静态成员被引用时

为了初始化一个新的封闭的构造类类型,首先那个特定封闭类型的一组新静态字段(§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);
}
登录后复制

0.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是D的基类。

给定先前的例子,为某些类型实参声明运算符,指定已经作为预定义转换而存在的转换是可能的。

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类型声明的运算符都不会与预定义的转换冲突。例如

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和I组成。该声明是无效的,因为任何使用相同类型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] 也就是在类型参数被替换成类型实参时,有可能替换后的实参导致出现两个成员使用相同的名字和签名。


以上就是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脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

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 04:48 PM

C 语言中符号的使用方法涵盖算术、赋值、条件、逻辑、位运算符等。算术运算符用于基本数学运算,赋值运算符用于赋值和加减乘除赋值,条件运算符用于根据条件执行不同操作,逻辑运算符用于逻辑操作,位运算符用于位级操作,特殊常量用于表示空指针、文件结束标记和非数字值。

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 的字符范围更大,并使用专门的函数进行算术运算。

c#多线程和异步的区别 c#多线程和异步的区别 Apr 03, 2025 pm 02:57 PM

多线程和异步的区别在于,多线程同时执行多个线程,而异步在不阻塞当前线程的情况下执行操作。多线程用于计算密集型任务,而异步用于用户交互操作。多线程的优势是提高计算性能,异步的优势是不阻塞 UI 线程。选择多线程还是异步取决于任务性质:计算密集型任务使用多线程,与外部资源交互且需要保持 UI 响应的任务使用异步。

char在C语言中如何进行类型转换 char在C语言中如何进行类型转换 Apr 03, 2025 pm 03:21 PM

在 C 语言中,char 类型转换可以通过:强制类型转换:使用强制类型转换符将一种类型的数据直接转换为另一种类型。自动类型转换:当一种类型的数据可以容纳另一种类型的值时,编译器自动进行转换。

C语言 sum 的作用是什么? C语言 sum 的作用是什么? Apr 03, 2025 pm 02:21 PM

C语言中没有内置求和函数,需自行编写。可通过遍历数组并累加元素实现求和:循环版本:使用for循环和数组长度计算求和。指针版本:使用指针指向数组元素,通过自增指针遍历高效求和。动态分配数组版本:动态分配数组并自行管理内存,确保释放已分配内存以防止内存泄漏。

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