zoukankan      html  css  js  c++  java
  • 2.C#2.0之泛型(完成)

      2.1泛型类型声明

         泛型类声明是一种类声明,只不过它比类多声明类型参数列表和类型参数约束语句。

         只有提供类型参数列表,这个类声明才能提供类型参数化约束语句;提供了类型参数列表的类声明就是一个泛型类声明,此外任何嵌入到泛型类声明/泛型结构声明中的类,自身也是一个泛型类声明,因为它们必须提供包含类型的类型参数以创建构造类型。

         构造类型可以是一个/多个参数,例如List<T>,被称为开放构造类型;也可不使用类型参数来构造类型,例如List<int>,被称为封闭构造类型。

         构造类型不可被重载,也就是说,和普通类型一样,在一个作用域,泛型类型必须被唯一地命名;但在非限定类型名字查找中使用的类型查找规则和成员访问,确实考虑到了类型参数的个数。

       2.1.1 类型参数

         类型参数在类声明上提供,每个类型参数都是一个简单的标识符,它指定用来创建一个构造类型的类型参数的占位符,即为后面将要被提供的类型的占位符,被提供的类型才是实际的类型,即类型实参。

         类声明中的每个类型参数在类的声明空间定义了一个名字,这个名字不能和另一个类型参数/类中成员具有相同的名字,更不能和类型自身有相同的名字;类型参数的作用域包括基类、类型参数约束语句、类体;与类成员不同的是,它不能扩展到派生类。

         由于类型参数可以被许多不同的实际类型参数所实例化,所以类型参数与其他类型相比,略微有些不同的操作和限制:

            *.类型参数不能用于直接声明一个基类型或接口;

            *.对于在类型参数上的成员查找规则,如果约束存在,则依赖于应用到该类型参数的约束;

            *.有效的类型参数转换依赖于应用到该类型参数上的约束(如果有的话);

            *.文本null不能被转换到由类型参数所给定的类型,除非类型参数是由一个类约束所约束的,但可以使用一个默认值表达式代替;

            *.由一个类型参数给定的类型的值可以使用"=="和"!="于null进行比较;

            *.如果类型参数通过一个构造函数约束被约束,则new表达式只能为类型参数所使用;

            *.类型参数不能用于特性内的任何地方;

            *.类型参数不能用于成员访问,也不能标识号一个静态成员或嵌套类型的类型名字;

            *.在不安全代码中,类型参数不能用做非托管类型。

         作为一种类型,类型参数纯粹只是一个编译时构件;在运行时,每个类型参数被绑定到运行时类型,这是通过泛型类型声明所提供的类型实参而指定的;为此,在运行时,使用类型参数声明的变量类型是一个封闭类型。

       2.1.2 实例类型

         每个类声明都有与之关联的构造类型,即实例类型;对于一个泛型类声明,实例类型是通过创建一个来自于类型声明的构造类型而形成的;由于实例类型使用类型参数,因此只有在类型参数的作用域内,实例类型才有效。实例类型在类声明中是this的类型;对于非泛型类,实例类型就是声明类型。

       2.1.3 基类规范

         在类声明中指定的基类/基接口可以是一个构造类型/构造接口类型,但基类/基接口自身不能是类型参数,但在其作用域可以包含类型参数。

         泛型类声明不能使用System.Attribute作为直接或间接基类。

         在一个类中的方法如果重写或实现基类或接口的方法,那么它必须为特定类型提供合适的方法:

       2.1.4 泛型类的成员

         泛型类的所有成员都可以使用封闭类中的类型参数,不论是直接使用还是作为构造类型的一部分。运行时实际类型参数会将其代替。

         在实例函数成员之内,this的类型就是声明的实例类型。

         除了使用类型参数作为类型之外,在泛型类声明中的成员也遵循和非泛型类成员相同的规则。其他的后面会写到~

       2.1.5 泛型类中的静态字段

         在一个泛型类声明中的静态变量,在相同封闭构造类型的所有实例中是被共享的,但在不同封闭构造类型的实例中,不被共享。有点绕口,举个栗子:

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

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

         当发生下面情况,则会有一个新的封闭构造函数类类型被首次初始化:

                 *.一个封闭构造类型的实例被创建;

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

         为了初始化一个新的封闭构造类类型,首先那个特定封闭类型的一组新静态字段将会被创建,每个静态字段都会被初始化为其的默认值;接着,将为这些静态字段执行静态字段初始化器。最后将执行静态构造函数。

         由于静态构造函数将为每个封闭构造类类型执行一次,所以对于无法在编译时通过约束进行的检查来说,在运行时去实施运行时检查将会很方便。

         这是使用静态构造函数检查一个类型参数是否是一个引用类型。

       2.1.7 访问受保护的成员

         在一个泛型类声明中,通过从泛型类构造的任何类型的实例,都可以对继承的受保护实例成员进行访问;尤其是访问被指定了protected和protected internal的实例成员的规则,进行了扩充:在一个泛型类G中,对于一个继承的受保护的实例成员M,可以使用E.M形式的基本表达式对一个继承的受保护实例成员进行访问,前提E的类型是从G构造的类类型,或是一个继承于从G构造的类类型的类类型。

       2.1.8 在泛型类中重载

         在一个泛型类声明中的方法、构造函数、索引器、运算符可以被重载。但为了避免在构造类中出现歧义,这些重载是受约束的。在同一个泛型类声明中使用相同的名称声明的两个函数成员必须具有这样的参数类型,也就是封闭构造类型中不能出现两个成员使用相同的名称和签名。当考虑所有可能的封闭构造类型时,这条规则包含了在当前程序中目前不存在的类型实参,但它仍然是可能出现的。在类型参数上的类型约束由于这条规则的目的而被忽略。

       2.1.9 参数数组

         类型参数可以用在参数数组的类型中。

       2.1.10 重写和泛型类

         和往常一样,在泛型类中的函数成员可以重写基类中的函数成员。如果基类是一个非泛型/封闭构造类型,那么任何重写函数成员不能有包含类型参数的组成类型;如果基类是一个开放构造类型,那么重写函数成员可以使用其声明中的类型参数。当重写基类成员时,基类成员必须通过替换类型实参来确定,一旦基类的成员被确定,则用于重写的规则和非泛型类是一样的。

       2.1.11 泛型类中的运算符

         发泛型类声明可以定义运算符,它遵循与常规类相同的规则。类声明的实例类型必须以一种类似于运算符的常规规则的方式在运算符中被使用。

         用到案例中就是:

         对于一个从源类型S到目标类型T的转换运算符,用到转换运算符的规则时,任何关联S或T的类型参数都被认为是唯一类型,它们与其他类型没有继承关系,并且在这些类型参数上的任何约束都将被忽略。

         第一个运算符声明是允许的,因为转换运算符的原因,T和int被认为是没关系的唯一类型,而C<T>是D<T>的基类。

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

         当类型object作为T的类型实参被指定时,第二个运算符声明一个已经存在的转换(从任何类型到object可以隐式,也可以显式转换)。在两个类型之间存在预定义转换的情况下,在这些类型上的所有用户定义的转换都将被忽略。尤其是:

         对于除了object的所有类型,有Nullable<T>类型声明的运算符都不会与预定义的转换冲突:

         然而,对于类型object,预定义的转换在所有情况隐藏了用户定义转换,有一种情况除外:

       2.1.12 泛型类中的嵌套类型

         泛型类声明可以包含嵌套类型声明。封闭类的类型参数可以在嵌套类型中使用;嵌套类型声明可以包含附加的类型参数,它只适用于该嵌套类型。

         包含在泛型类声明中的每个类型声明都是隐式的泛型类型声明。当编写一个嵌套在泛型类型内的类型引用时,包含构造类型,包括它的类型实参,必须被命名。然后,在外部类,内部类型可以被无限制地使用;当构造一个内部类型时,外部类的实例类型可以被隐式使地使用。

         尽管这不是一种很好的编程习惯,但嵌套类型中的类型参数可以隐藏一个成员,或者外部类型中声明的一个类型参数。

       2.1.13 应用程序入口点

         应用程序入口点方法不能存在于一个泛型类声明中。

      2.2泛型结构声明

         与类声明一样,结构声明可以有可选的类型参数。除了声明差别之外,泛型类声明的规则也使用于泛型结构声明。

      2.3泛型接口声明

         使用类型参数声明的接口是一个泛型接口声明。在接口声明中的每个类型参数在接口的声明空间定义了一个名字,在一个接口上的类型参数的作用域包括基接口、类型约束语句和接口体。在其作用域之内,类型参数可以用做一个类型。应用到接口上的类型参数和应用到类上的类型参数具有相同的限制。

         泛型接口中的方法与泛型类中的方法遵循相同的重载规则。

       2.3.1 实现接口的唯一性

         由泛型类型声明实现的接口必须为所有可能的构造类型保留唯一性。没这条规则,将不可能为特定的构造类型确定正确的调用方法。例如:

         这样是不允许的,因为无法执行下面这段代码:

         为了确定一个泛型类型声明的接口列表是有效的,可以按下面的步骤进行:

         在类声明X之上,接口列表L由I<U>和I<V>组成。该声明是无效的,因为任何使用相同类型U和V的构造类型,都会导致两个接口是同一的。

       2.3.2 显式接口成员实现

         使用构造接口类型的显式接口成员实现本质上与简单接口类型方式上是相同的。和以往一样,显式接口成员实现必须由一个指明哪个接口被实现的接口类型来限定。该类型可能是一个简单接口或构造接口。

      2.4泛型委托声明

         委托声明可以包含类型参数。

         使用类型参数声明的委托是一个泛型委托声明。委托声明只有在智齿类型参数列表时,才能支持类型参数的约束语句。除此之外,泛型委托声明和常规的委托声明遵循相同的规则。泛型委托声明中的每个类型参数在于委托关联的特定声明空间定义了一个名字;在委托声明中的类型参数的作用域包括返回类型、正式参数列表、类型参数约束语句。

         像其他泛型类型声明一样,必须给定类型实参才能形成构造委托类型。构造委托类型的参数和返回值,由委托声明中构造委托类型的每个类型参数对应的实参替代所形成;而结果返回类型和参数类型用于确定什么方法与构造委托类型兼容。例如:

      2.5构造类型

         泛型类型声明自身并不表示一个类型,相反,泛型类型声明通过应用类型实参的方式用做形成许多不同类型的"蓝图";类型实参被写在"<"和">"之间,并紧随泛型类型声明名称之后;使用至少一个实参而命名的类型称为构造类型。

         当一个命名空间或类型名称被计算时,只有带有正确数量类型参数的泛型类型会被考虑。所以只要类型有不同数量的类型参数,且声明在不同的命名空间,那么就可以使用相同标识符标识不同类型,这对于在同一程序中混合使用泛型和非泛型类是很有用的。

         类型名称可能标识一个构造类型,尽管没有直接指定类型参数;这种情况在一个类型嵌套在一个泛型类声明中就会出现,并且包含声明的实例类型将因为名称查找而被隐式地使用。

       2.5.1 类型实参

         在一个类型实参列表中的每个实参都是一个类型;类型参数反过来也可以是构造类型/类型参数。在不安全代码中,类型实参不能是指针类型。

       2.5.2 开放类型和封闭类型

         所有类型都可以分为开放类型或封闭类型。开放类型是包含类型参数的类型,在数组类型只有当数组元素是开放类型时才算开放类型;在构造类型只有类型实参中的一个/多个是开放类型时才算开放类型。

         在运行时,在泛型类型声明中的代码都是在一个封闭构造类型的上下文中执行,这个封闭构造类型是通过将类型实参应用到泛型声明中创建的;在泛型类型中的每个类型实参被绑定到一个特定运行时类型,所有语句和表达式的运行时处理总是针对封闭类型发生,而开放类型只发生在编译时处理的过程。

         每个封闭构造类型都有它自己的一组静态变量,它们并不被其他封闭类型共享;因为运行时不存在开放类型,所以开放类型没有关联的静态变量。如果两个封闭构造类型是从同一个类型声明构造的,并且对应的类型实参也是相同的类型,那么它们就是相同的类型。

       2.5.3 构造类型的基类和接口

         构造类类型有一个直接基类,很像是一个简单类类型。如果泛型类声明没有指定基类,则基类为object;如果制定了,那么对于这个构造类型的基类,可以通过把在基类声明中的每个类型参数替代为这个构造类型的对应类型实参而得到。比如:

         如果构造类型是G<int>,那么它的基类会是B<string,int[]>。

         类似构造类、结构、接口类型有一组显式的基接口,显式基接口通过接受泛型类型声明中的显式基接口声明和某种替代而形成,这种替代是把在基接口声明中的每个类型参数,替代为构造类型的对应类型实参。一个类型的所有基类和基接口集合可以通过递归得到基类和接口的直接基类与接口而形成。比如:

       2.5.4 构造类型的成员

         构造类型的非继承成员通过将成员声明中的每个类型参数替换成类型实参得到的。比如:

         构造类型Gen<int[],IComparable<string>>有以下成员:

         这里要注意,替代处理是基于类型声明的语义意义的,并不是简单的基于文本的替代。

         构造类型的继承成员以一种相似的方法获得。首先,直接基类的所有成员是已经确定的,如果基类自身是构造类型,这就可能包括刚才提到的递归应用,然后将每个类型参数替代为构造类型对应类型实参而被转换。比如:

         在这个例子中,构造类型D<int>的非继承成员public int G(string s)通过替代类型参数T的类型实参int而得到;对于继承成员,首先确定B<T[]>的成员而被确定,B<T[]>成员的确定通过将U替换为T[],产生public T[] F(long index),然后类型实参int替换类型参数T,产生继承成员public int[] F(long index)。

       2.5.5 构造函数的可访问性

         如果构造类型的所有部分是可访问的,那么它就是可访问的。具体说就是:如果泛型类型名是public,且所有类型参数也是public,那么构造类型的可访问性就是public;如果类型名或类型实参之一是private,那么构造类型的可访问性就是private;如果类型实参之一的可访问性是protected,另一个是internal,那么构造类型的可访问性仅限于该类以本程序及之内的子类。

         所以,构造类型的可访问域是它各个组成元素可访问域的交集。

       2.5.6 转换

         构造类型遵循与非泛型类型相同的转换规则,之前在第六章写到过。当应用这些规则时,构造类型的基类和结构必须按照上面提到的方式确定构造类型的基类和接口。

         除了在第六章中所描述的,构造引用类型之间不存在特别的转换。尤其是,与数组类型不同,构造引用类型不允许"con-variant"(共变类型)转换,就是类型List<B>不能转换到类型List<A>,无论显式还是隐式,即使B派生于A也不行;同时也不存在从List<B>到List<object>的转换。对于这点的基本原理很简单:如果可以转换的话,你就可以将一个类型A的值存储到B的list中,破坏了list<B>类型列表中的只能有类型B值的这种不变性。

       2.5.7 System.Nullable<T>类型

         在.NET基类库中定义了System.Nullable<T>泛型结构类型,它表示一个可以为null的类型T的值。System.Nullable<T>类型在很多情况下是很有用的,例如可用于指示数据库表的可空列,或者XML元素中的可选特性。

         可以从null类型向任何由System.Nullable<T>类型构造的类型做隐式转换。这种转换结果就是System.Nullable<T>的默认值。也就是可以这样写:

       2.5.8 使用别名指令

         使用别名可以命名一个封闭构造类型,但不能命名一个没有提供类型实参的泛型类型声明。比如:

       2.5.9 特性

         开放类型不能在特性内的任何地方使用;封闭构造类型可以用做特性的实参,但不能用做特性名,因为System.Attribute不可能是泛型类声明的基类型。

      2.6泛型方法

         泛型方法是与特定类型相关的方法。除了常规参数之外,泛型方法还命名了在使用方法时需要提供的一组类型参数。泛型方法可以在类、结构或接口声明中声明,而类、结构或接口可以是泛型,也可以不是泛型。

         如果一个泛型方法在一个泛型类型声明中被声明,那么方法体可以引用方法的类型参数和包含声明的类型参数。

         泛型方法是通过在方法的名字之后放置类型参数列表来声明:

         类型参数列表和类型参数约束语句与泛型类型声明具有同样的语法和功能。由类型参数列表声明的类型参数作用域贯穿整个泛型方法声明,他可以用于形成贯穿包括返回值、方法体和类型参数约束语句(不包括特性)在内的该作用域的类型。

         上面这个例子就是类型方法,意思是如果满足Test委托,就找到数组中的第一个元素。

       2.6.1 泛型方法签名

         为了签名的比较,任何参数约束和类型参数的名字都会被忽略,但类型参数的个数和顺序是很重要的。

         泛型方法的重载采用了一条与在泛型类型声明中管理方法重载相似的规则,进一步地进行了限制。两个使用相同名称和相同数量的类型实参的两个泛型方法声明,只能具有无封闭类型实参列表的参数类型,当它们以相同顺序应用到两个方法上时,将产生两个具有相同签名的方法。由于这条规则约束将不予考虑。

       2.6.2 虚拟泛型方法

         泛型方法可以用abstract,virtual,override修饰符来声明。当为重载或接口实现进行方法匹配时,将使用上面写到的签名匹配规则;当泛型方法重写在基类中声明的泛型方法,或实现基接口中的方法时,为每个方法类型参数给定的约束在两个声明中必须是相同的,在这里方法类型参数由原始位置按从左到右的顺序来标识。

         F的重写是正确的,因为类型参数名称可以不同;G的是错误的,因为给定的类型参数约束与被重写方法的不匹配。

       2.6.3 调用泛型方法

         泛型方法调用可以显式地指定类型实参列表,也可以省略类型实参列表从而依靠类型推断来确定类型参数。关于方法调用的确切编译时处理和类型推断在后面会写到;下面这个例子,时在类型推断和类型实参替代参数列表后,重载决策是如何发生的。

       2.6.4 类型实参推断

         当不指定类型实参而调用泛型方法时,类型推断处理将试图为这个调用推断类型实参。类型推断的存在可以使调用泛型方法时,采用更方便的语法,并且可以避免程序员指定冗余的类型信息。

         通过类型推断,类型实参int和string由方法的实参确定。

         类型推断作为方法调用编译时处理的一部分发生,并且在调用的重载决策之前发生。

         当在一个方法调用中指定特性的方法组时,类型实参不会作为方法调用的一部分而指定,类型推断将被应用到方法组中的每个泛型方法:如果类型推断成功,则被推断的类型实参被用于确定后续重载决策的实参类型,如果重载决策选择了将要调用的泛型方法,则被推断的类型实参将用做调用的实际类型实参;如果类型推断失败,则这个方法将不参与重载决策。

         类型推断失败自身不会产生编译时错误,但重载决策没找到合适的方法时,将导致编译时错误。

         如果所提供的实参个数与方法的参数个数不同,推断立刻失败;若相同,类型推断将为提供给方法的每个正式实参独立地进行处理。将定这个实参的类型为A,对应参数的类型为P,则类型推断将按下列步骤关联类型A和P而发生:

              *.如果以下任何一条成立,将不能从实参推断出任何东西,但类型推断是成功的:

                     1.P不调用方法的任何方法类型参数;

                     2.实参是null字符;

                     3.实参是一个匿名方法;

                     3.实参是一个方法组;

              *.如果P是数组类型,A是同秩的数组类型,那么替换A和P,并重复这个步骤;若不是同秩,那么类型推断失败;

              *.如果P是方法的类型参数,那么类型推断成功,并且A是那个类型实参所推断的类型

              *.否则P必须是构造类型。如果对于出现在P中的每个方法类型参数Mx,恰好可以确定一个类型Tx,使用每个Tx替换每个Mx,这将产生一个额类型,对于这个类型,A可以通过标准的隐式转换而被转换,那么类型推断成功,对于每个Mx,Tx就是推断的类型。方法类型参数约束因为类型推断的原因将被忽略。如果对于一个给定的Mx,没有Tx存在或者多于一个Tx存在,那么泛型方法的类型推断将会失败。

         如果所有的方法实参都通过先前的算法进行了成功的处理,那么由实参而产生的所有推断都将被汇聚,这组推断必须有如下的属性:

              *.方法的每个类型参数必须有一个为其推断的类型实参,意思就是这组推断必须是完整的;

              *.如果类型参数出现多于一次,那么对那个类型参数的所作的推断都必须推断相同的类型实参,意思就是这组接口必须是一致的。

         如果能够找到一组完整且一致的推断类型实参,那么类型推断就可以说是成功的。

       2.6.5 语法歧义

         在简单名称和成员访问中,对于表达式来说容易引起语法歧义。比如:F(G<A,B>(7)),可以被解释为对带有两个参数(G<A)、(B>(7))的F的调用;同样还可以说对带有一个实参的F方法的调用,这个实参是对带有两个类型实参和一个正式实参的泛型方法G的调用。

         如果表达式可以以两种不同的有效方法来解析,其中">"可以被解析为运算符的全部或一部分,或作为类型实参列表的一部分,那么就先解析紧随">"之后的标记将会被检查。如果它是如下之一,那么">"被解析为类型实参列表,否则">"被解析为一个运算符

       2.6.6 对委托使用泛型方法

         委托的实例可通过引用一个泛型方法声明而创建。当通过委托调用泛型方法时,所使用的类型实参将在委托实例化时确定。类型实参可以通过类型实参列表显式给定,也可以通过类型推断确定;如果采用类型推断,委托的参数类型将用做推断处理过程的实参类型,委托的返回类型不用于推断。

       2.6.7 非泛型属性、时间、索引器或运算符

         属性、事件、索引器、运算符自身可以没有类型参数,尽管它们可以出现在泛型类中,且可以从一个封闭类中使用类型参数;如果需要一个类似属性的泛型构件,那么必须使用泛型方法。

      2.7约束

         泛型类型和方法声明可以通过在声明中包含类型参数约束语句,有选择地指定类型参数进行约束。

         每个类型参数约束语句由标记where,后紧跟类型参数的名字、冒号、类型参数的约束列表组成;每个类型参数有且只有一个where从句。与属性访问器中的get和set标记相似,where标记不是关键字。

         where从句中给定的约束列表按这样的顺序包含下列组件:一个单一的类约束、一个/多个接口约束,构造函数约束new()。

         如果约束是类类型或接口类型,则这个类型指定类型参数必须支持每个类型实参的最小"基类型"。无论什么时候用一个构造类型或泛型函数,在编译时类型实参对于类型参数上的约束都会被检查,所提供的类型实参必须派生自给定的约束,或实现为该类型参数设定的所有约束。

         被指定为类约束的类型必须遵循下面的规则:

         在很多情况,约束可以包含任何关联类型的类型参数,或者方法声明作为构造类型的一部分,且可以包括正在被声明的类型,但约束不能是单一的类型参数。被指定为类型参数约束的任何类或接口类型,必须至少与泛型类型或正被声明的方法具有相同的可访问性。

         如果一个类型参数的where语句包括new()形式的构造函数约束,则可以使用new运算符创建该类型的实例是可能的。用于带有一个构造函数约束的类型参数的任何类型实参必须有一个无参的构造函数。

         下面的例子是一个错误,因为它试图直接使用一个类型参数作为约束:

       2.7.1 满足约束

         无论什么时候使用构造类型或引用泛型方法,所提供的类型实参都要针对声明在泛型类型或方法中的类型参数约束做出检出。对于每个where语句,对应的类型参数的类型实参A将按下面内容对每个约束做出检出:

              *.如果约束是一个类类型或接口类型,让C标识提供的类型参数约束,该类型实参将替代出现在约束中的任何类型参数。为了满足约束,类型A必须按下面方面转化成C:

                       -同一转换

                       -隐式引用转化

                       -装箱转换

                       -从类型参数A到类型参数C的隐式转换

              *.如果约束是new(),则类型参数A不能是abstract,并且必须有一个公有的无参构造函数。如果下面有一个为真,就可以得到满足:

                       -A是一个值类型(所有值类型都有一个共有默认构造函数)

                       -A是一个非abstract类,并且A包含一个无参公有构造函数

                       -A是一个非abstract类,并且有一个默认构造函数

         如果给定的类型实参不能满足约束,将会出现编译时错误。且因为类型实参不能被继承,所以约束也不能被继承。

         D必须在其类型参数T上指定约束,以满足由基类B<T>所施加的约束;E不需要,因为对于任何T,List<T>都能实现了IEnumerable接口。

       2.7.2 类型参数上的成员查找

         在由类型参数T给定的类型中,成员查找的结果取决于T所制定的约束。如果T没有约束,或只有new()约束,则在T上的成员查找与在object上的成员查找一样,返回一组相同的成员;否则成员查找的第一阶段,将考虑T所约束的每个类型的所有成员,结果将会被合并,然后会将隐藏成员从合并结果中删除。

         在泛型出现之前,成员查找总是返回一组在类中唯一声明的成员,或者返回一组在接口中唯一声明的成员,并且类型可能是object。在类型参数上的成员查找做出了一些改变:当一个类型参数有一个类约束和一个/多个接口约束时,成员查找可以返回一组成员,这些成员有的是在类中声明,有的是在接口中声明 。下面的附加规则处理:

              *.在成员查找过程中,在除了object之外的类中声明的成员隐藏了在接口中声明的成员;

              *.在方法和索引器的重载决策过程中,如果任何可用成员在一个不同于object的类中声明,那么在接口中声明的所有成员都将从被考虑的成员集合中删除。

         这些规则只在将一个类约束和接口约束绑定到类型参数上才有效,且大致意思就是类的成员,对于接口成员总是首选。

       2.7.3 类型参数和装箱

         当一个结构类型重写继承于System.Object(Equals,GetHashCode,ToString)的虚拟方法时,通过结构类型实例调用虚拟方法将不会导致装箱,即使结构用做一个类型参数,并且通过类型参数的类型实例来调用,情况也是如此。

         三次调用的结果就是:1 2 3。

         当在一个约束的类型参数上访问一个成员时,装箱绝不会隐式发生。

         有一个接口ICounter包含一个方法Increment,该方法可以用来修改一个值。如果ICounter用做一个约束,则Increment方法的实现将通过Increment在其上被调用的变量的引用而被调用,这个变量不是一个装箱拷贝。对于Increment的首次调用是修改了变量x的值,第二次修改的是x装箱后的拷贝,所以输入结果是:0 1 1。

       2.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的基类,以及任何C实现的接口;

              -从C到T的显式引用转换,从C的基类到T,以及C实现的任何接口到T;

              -如果存在从C到A的隐式用户定义转换,从T到A的隐式用户定义转换;

              -如果存在从A到C的显式用户定义转换,从A到T的显式用户定义准换;

              -从null类型到T的隐式转换。

         一个带有元素类型T的数组类型具有object和System.Array之间的互相转换。如果T有作为约束而指定的类类型C,将会有如下附加规则:

         上面的规则不允许从非约束类型参数到非接口类型的直接隐式转换,是因为为了防止混淆,并且使得这种转换得语义更明确。

      2.8表达式和语句

         某些表达式和语句得操作针对泛型进行了修改。

       2.8.1 默认值表达式

         默认值表达式用于获取一个类型的默认值。通常一个默认值表达式用于类型参数,因为如果类型参数是一个值类型/引用类型,它可能不是已知的。

         如果在一个默认值表达式中使用一个基本表达式,这个表达式如果不是一个类型,那么将会导致编译时错误。然后再第七章描述的规则也适用于形成E.default的构件。

         如果默认值表达式的左边针对一个引用类型在运算时被计算,则结果将null转换到那个类型;如果是一个值类型,则结果是值类型的默认值。

         如果类型是一个具有类约束的引用类型/类型参数,则默认值表达式是一个常量表达式。如果类型是下列值之一,则默认值表达式是一个常量表达式:

       2.8.2 对象创建表达式

         对象创建表达式的类型可以是一个类型参数。当类型参数被作为对象创建表达式中的类型而指定时,必须具备两个条件:

              1.实参列表必须删;

              2.必须为类型参数指定new()形式的构造函数约束。

         通过创建一个类型参数被绑定到的运算时类型的实例,并调用该类型的默认构造函数,就可以执行对象创建表达式。运行时类型可以是引用类型/值类型。

       2.8.3 typeof运算符

         typeof运算符可以用于类型参数。其结果是绑定到类型参数的运行时类型的System.Type对象。typeof运算符也可以被用于构造类型。

         结果是:System.Int32     X<X<System.Int32>>

         Typeof运算符不能用于没有指定类型实参的泛型类型声明的名字。

       2.8.4 引用相等运算符

         如果T由一个类约束而约束,引用类型相等运算符可以用于比较类型参数T的值。引用类型相等运算符的用处是可以让类型参数T的实参很容易地与其他为null的实参进行比较,即使T没有类约束也如此。在运行时,如果T是一个值类型,那么结果将是false。

         这个例子是检查一个非约束类型参数类型的实参是否为null。

       2.8.5 is运算符

         在开放类型上的is运算符操作遵循通常的第七章写的规则。如果e或T的编译时类型是一个开放类型,那么在运行时对e和T总是执行动态类型检查。

       2.8.6 as运算符

         只有在T有一个类约束时,类型参数T才可以用在as运算符的右边。这种限制是必须的,因为值null可能作为运算符的结果返回。

         在as运算符的当前规范中,对于表达式:e as T,如果从e的编译时类型到T不存在有效的显式引用转换,将会出现编译时错误;但在泛型,这条规则做了修改:如果e的编译时类型或T是一个开放类型,在这种情况下将不会出现编译时错误;相反,还会执行运行时检查。

       2.8.7 异常语句

         对于开放类型,throw和try的通常规则是适用的:

              -只有类型参数具有System.Exception异常(或子类具有)作为类约束,throw语句才可以用做一个表达式,这个表达式的类型由一个类型参数给定;

              -只有类型参数具有System.Exception异常(或子类具有)作为类约束,在catch语句中的命名的类型才可以是一个类型参数。

       2.8.8 lock语句

         lock语句可以用做其类型由一个类型参数给定的表达式。如果表达式的运行时类型是一个值类型,则lock没有效果。

       2.8.9 using语句

         using语句遵循通常的规则:表达式必须被隐式地转换到System.IDisposable。如果类型参数通过System.IDisposable约束,那么表达式可以使用using。

       2.8.10 foreach语句

         给定如下形式的foreach语句:

         如果collection表达式是一个没有实现集合模式的类型,但它为类型T实现了构造接口System.Collections.Genric.IEnumerable<T>,那么foreach语句的扩展会是:

  • 相关阅读:
    JEECG开发总结
    ehcache集群的配置
    spring拦截器
    spring的基本配置
    kindeditor编辑器
    jQuery中的Ajax
    表单验证
    Python中的Random模块
    Open vSwitch FAQ (二)
    Open vSwitch FAQ (一)
  • 原文地址:https://www.cnblogs.com/dreamoffire/p/10173840.html
Copyright © 2011-2022 走看看