`
cdragon
  • 浏览: 76971 次
  • 性别: Icon_minigender_1
  • 来自: 北京
最近访客 更多访客>>
社区版块
存档分类
最新评论

effective hierarchy(一)之 基本概念结束篇

阅读更多

一、函数/功能成员(function member)

 

(1)概念

  

     函数成员是包含可执行语句的那些成员;函数成员始终是类型的成员,而不能是名字空间的成员。

      C#的函数成员包括的类别(category):方法、属性、事件、索引器、用户定义的操作符、实例构建器、静态构建器、析构器

 

      ~除了不能显式调用静态构建器和析构器,其它都可通过函数成员调用(function member invocation,简为fmi)来执行。函数成员调用的实际书写语法,取决于特定的函数成员类别;

      ~fmi的自变量(argument)列表,为函数成员的参数(parameter)提供了实际值或变量引用(variable references);

      ~方法、索引器、操作符和实例构建器的调用采取重载方案(resolution)以确定调用函数成员的哪一个候选集(candidate set);

      ~编译时,一旦(once)某个特定的函数成员被识别(identified)出,就可能通过重载方案,执行调用函数成员的实际运行时处理,此过程不在这里详述;

      ~下面摘要了能够被显式调用的六个类别的函数成员--发生在构造内(in construct)的处理(e,x,y和value代表归类为变量或值的表达式,T代表归类为类型的表达式,F是方法的简单名称,P是属性的简单名称):

 

      a.方法调用

         ~F(x, y) 应用重载决策以在包含类或结构中选择最佳的方法 F。用参数列表 (x, y) 调用该方法。如果该方法不为 static,则用 this 来表达对应的实例。
         ~T.F(x, y) 应用重载决策以在类或结构 T 中选择最佳的方法 F。如果该方法不为 static,则发生编译时错误。用参数列表 (x, y) 调用该方法。
         ~e.F(x, y) 应用重载决策以从e所属的类型确定的类、结构或接口中选择最佳的方法 F。如果该方法为 static,则发生编译时错误。用实例表达式e和参数列表 (x, y) 调用该方法。

 

      b.属性访问

         ~P 调用包含类或结构中属性 P 的 get 访问器。如果 P 是只写的,则发生编译时错误。如果 P 不为 static,则实例表达式为 this。
         ~P = value 用参数列表 (value) 调用包含类或结构中的属性 P 的 set 访问器。如果 P 是只读的,则发生编译时错误。如果 P 不为 static,则用 this 来表达对应的实例。
         ~T.P 调用类或结构 T 中属性 P 的 get 访问器。如果 P 不为 static,或者如果 P 是只写的,则发生编译时错误。 
         ~T.P = value 用参数列表 (value) 调用类或结构 T 中的属性 P 的 set 访问器。如果 P 不为 static,或者如果 P 是只读的,则发生编译时错误。
         ~e.P 用实例表达式 e 调用由 e 的类型提供的类、结构或接口中属性 P 的 get 访问器。如果 P 为 static,或者如果 P 是只写的,则发生编译时错误。
         ~e.P = value 用实例表达式 e 和参数列表 (value) 调用 e 的类型给定的类、结构或接口中属性 P 的 set 访问器。如果 P 为 static,或者如果 P 是只读的,则发生编译时错误。

 

     c.事件访问

        ~E += value 调用包含类或结构中的事件 E 的 add 访问器。如果 E 不是静态的,则用 this 来表达对应的实例。  ~E -= value 调用包含类或结构中事件 E 的 remove 访问器。如果 E 不是静态的,则用 this 来表达对应的实例。
        ~T.E += value 调用类或结构 T 中事件 E 的 add 访问器。如果 E 不是静态的,则发生编译时错误。
        ~T.E -= value 调用类或结构 T 中事件 E 的 remove 访问器。如果 E 不是静态的,则发生编译时错误。
        ~e.E += value 用实例表达式 e 调用由 e 的类型提供的类、结构或接口中事件 E 的 add 访问器。如果 E 是静态的,则发生编译时错误。
        ~e.E -= value 用实例表达式 e 调用由 e 的类型给定的类、结构或接口中事件 E 的 remove 访问器。如果 E 是静态的,则发生编译时错误。

 

    d.索引器访问

       ~e[x, y] 应用重载决策以在 e 的类型给定的类、结构或接口中选择最佳的索引器。用实例表达式 e 和参数列表 (x, y) 调用该索引器的 get 访问器。如果索引器是只写的,则发生编译时错误。
       ~e[x, y] = value 应用重载决策以在 e 的类型给定的类、结构或接口中选择最佳的索引器。用实例表达式 e 和参数列表 (x, y, value) 调用该索引器的 set 访问器。如果索引器是只读的,则发生编译时错误。

 

    e.运算符调用

       ~-x 应用重载决策以在 x 的类型给定的类或结构中选择最佳的一元运算符。用参数列表 (x) 调用选定的运算符。
       ~x + y 应用重载决策以在 x 和 y 的类型给定的类或结构中选择最佳的二元运算符。使用参数列表 (x, y) 调用选定的运算符。

 

    f.实例构造函数

      ~new T(x, y) 应用重载决策以在类或结构 T 中选择最佳的实例构造函数。用参数列表 (x, y) 调用该实例构造函数。

 

(2)自变量列表(argument lists)

 

    a. 每个功能成员调用(fmi)包括一个自变量列表(argument list),该列表为功能成员的参数提供着实际值或变量引用;fmi的自变量列表的“固化”语法(syntax for specifying)是由功能成员的归类(category)决定的。

        ~对实例构建器、方法和委托,自变量是自变量列表;语法格式如下:

           argument-list:
      argument
                argument-list   ,   argument
           argument:
      expression
                ref   variable-reference
                out   variable-reference

        ~对属性,调用get访问器(accessor)时自变量列表清空(empty);调用set访问器时,由赋值操作符的右操作数标明的表达式组成自变量列表;

        ~对事件,由+=/-=操作符标明的右操作数的表达式组成自变量列表;

        ~对索引器,由索引器访问(access)中方括号之间标明的表达式构成自变量列表;调用set访问器时,赋值操作符的右操作数的表达式组成自变量列表;

       ~对用户自定义的操作符,一元操作符的单操作数或二元操作符的两个操作数构成自变量列表;

 

    b.属性、事件、索引器和用户自定义的操作符的自变量(arguments)总是使用数值参数传递,功能成员的这些归类(category)不支持引用参数和输出参数;

 

    c.自变量列表由逗号分开的一个或多个自变量组成;(每)自变量使用下列格式之一:

       ~表达式

       ~关键字ref后跟变量引用

       ~关键字out后跟变量引用  

 

    d.fmi在运行时段,自变量列表的表达式或变量引用按从左到右的顺序计算:

       ~对于数值参数,计算自变量表达式,执行到相应参数类型的隐式转换。结果值成为fmi中数值参数的初始值。

       ~对于引用参数或输出参数,作为计算变量引用结果的存储位置,成为fmi中的参数表示的存储位置。如果作为引用参数或输出参数给定的变量引用,是一个引用类型(reference-type)的数组元素,则执行一个运行时检查以确保该数组的元素类型与参数的类型相同。如果检查失败,则引发 System.ArrayTypeMismatchException

 

    e.方法、索引器和实例构造函数可以将其最右边的参数声明为参数数组。是以正常形式(normal form)还是以扩展形式(expanded form)调用这类函数成员取决于哪种形式适用:

       ~当以正常形式调用带有参数数组的函数成员时,作为参数数组给定的自变量,必须是某个类型(a type)的单一(single)表达式,该类型隐式可转换为参数数组(parameter array type)的类型。在此情况下,参数数组的作用(act as)完全(precisely like)类似于数值参数。

       ~当以扩展形式调用带有参数数组的函数成员时,调用必须为参数数组(parameter array)指明零个或多个自变量,其中每个自变量都是某个类型(a type)的表达式,该类型隐式可转换为参数数组元素的类型(element type)。在此情况下,调用会创建一个(该)参数数组类型的实例,该实例的长度对应于自变量的个数,并用给定的自变量值初始化这个数组实例的每个元素,然后用新创建的数组实例作为实际参数(actual argument)。

     注意:

     ~只要存在从B到A的隐式引用转换(implicit reference conversion),数组协差规则(co-variance rules)允许一个数组类型A[]的值成为另一个数组类型B[]的实例的引用(reference),所以,当把引用类型(reference type)的数组元素作为引用参数或输出参数传递时,必须执行运行时检查(run-time check),以确保该数组的实际元素类型与参数的类型完全一致(identical)。见下面的示例,

         

class Test
{
   static void F(ref object x) {...}
   static void Main() {
      object[] a = new object[10];
      object[] b = new string[10];
      F(ref a[0]);      // Ok
      F(ref b[1]);      // ArrayTypeMismatchException
   }
}

//第二次函数调用发生错误!

 

    ~当以扩展形式调用带有参数数组的函数成员时,其调用处理过程完全类似于(as if)--带有数组初始值设定项(array initializer)的一个数组创建表达式(array creation expression)--被插入到扩展参数处(inserted around)。

    

//声明如下的函数
void F(int x, int y, params object[] args);

//执行函数调用
F(10, 20);
F(10, 20, 30, 40);
F(10, 20, 1, "hello", 3.0);

//等效于..
F(10, 20, new object[] {}); //为参数数组给定零个参数时,会创建空数组
F(10, 20, new object[] {30, 40});
F(10, 20, new object[] {1, "hello", 3.0});

 

(3)重载方案/策略(overload resolution)

 

    a.重载方案是在编译时,一旦给出参数列表和一整套候选函数成员(candidate)供选择时,便能够挑出最

       恰当的函数成员来调用的机制。在c#中,它主要用于以下函数调用(invocation)语境(context)中:

       ~调用表达式中(invocation-expression)用到的(named)方法调用;
       ~对象创建表达式中用到的实例构建器调用;
       ~通过元素访问形式(element-access)用到的索引访问器(indexer accessor)调用;
       ~表达式内用到的(referenced)预定义或自定义操作符.

 

       以上每个"环境"都以自己的唯一方式来定义候选函数成员和参数列表的(list of arguments)集合。例如,"调用方 法"(method invocation)不包括标记为override的方法,另外一种情况,如果派生类中方法可用(is applicable),那么基类中的方法就不作为"侯选"(not candidate)。

 

       一旦标定出候选的函数成员和参数列表,选择最佳函数成员(best function member)是按照以下规则进行:

       •如果给定了适用的候选函数成员集,则在其中选出最佳函数成员。
       •如果该集合只包含一个函数成员,则该函数成员为最佳函数成员。
       •否则,最佳函数成员的选择依据是:各成员对给定的参数列表的匹配程度。比所有其他函数成员匹配得更好的那个函数成员就是最佳函数成员,但有一个前提:必须使用 第 7.4.2.2 节 中的规则将每个函数成员与所有其他函数成员进行比较。
       •如果不是正好有一个函数成员比所有其他函数成员都好,则函数成员调用不明确(ambiguous)并发生编译时错误。 

 

     b.适用的函数成员

 

        当函数成员满足下列所有条件时,就称它是对(with respect to)参数列表A--可应用的(applicable)。

       ~A的参数数目与函数成员声明的参数数目相同。
       ~对A的每个参数,自变量的参数传递模式(passing mode of the argument)--包括值、ref 或 out,与相应参数的参数(parameter)传递模式相同,而且

       ~~对值参数或参数数组,存在从自变量类型到相应参数的类型的隐式转换,或
         ~~对于 ref 或 out 参数,自变量的类型( type of the argument )与相应参数的(parameter)类型相同。ref 或 out 参数毕竟(After all)只是传递的自变量的别名(alias)。

 

         包含参数数组的函数成员,如果按照上述规则是可应用的,则把它称作按常规形式适用(applicable in its normal form),反之,它可能是以扩展形式适用(applicable in its expanded form)。

         ~如果 A 中的自变量比函数成员声明中的固定参数的数目少,则该函数成员的展开形式无法构造(这是因为构造扩展形式的方法是:用参数数组的元素类型的零个或多个值参数,替换函数成员声明中的参数数组,使参数列表 A 中的参数数目匹配总的参数数目。),且该该函数成员不适用。

        ~如果在声明函数成员的类、结构或接口中,已经包含另一个与扩展形式具有相同签名的适用函数成员,则不适用扩展形式。

        ~除此以外,如果对于 A 中的每个自变量,它的参数传递模式与相应参数的参数传递模式相同,并且下列条件成立,则称该成员函数以展开形式适用:
          ~~对于固定值参数或展开操作所创建的值参数,存在从自变量的类型到相应参数的类型的隐式转换,或者
          ~~对于 ref 或 out 参数,参数的类型与相应参数的类型相同。 

 

    c.更好的函数成员

 

      给定一个带有参数类型集 {A1, A2, ..., AN} 的参数列表 A 和带有参数类型 {P1, P2, ..., PN} 和 {Q1, Q2, ..., QN} 的两个可应用的函数成员 MP 和 MQ,则在以下情况中,MP 定义为比 MQ 更好的函数成员:

      ~对于每个参数,从 AX 到 PX 的隐式转换都不比从 AX 到 QX 的隐式转换差(not worse),并且
      ~对于至少一个参数,从 AX 到 PX 的转换比从 AX 到 QX 的转换更好(better)。
      当执行此计算时,如果 MP 或 MQ 以扩展形式适用,则 PX 或 QX 所代表的是扩展形式的参数列表中的参数。

 

    d.更好的转换

 

       假设有一个从类型 S 转换到类型 T1 的隐式转换 C1,和一个从类型 S 转换到类型 T2 的隐式转换 C2,将按下列规则确定这两个转换中哪个是更好的转换:

       ~如果 T1 和 T2 是相同类型,则两个转换都不是更好的转换。
       ~如果 S 为 T1,则 C1 为更好的转换。
       ~如果 S 为 T2,则 C2 为更好的转换。
       ~如果存在从 T1 到 T2 的隐式转换,且不存在从 T2 到 T1 的隐式转换,则 C1 为更好的转换。
       ~如果存在从 T2 到 T1 的隐式转换,且不存在从 T1 到 T2 的隐式转换,则 C2 为更好的转换。
       ~如果 T1 为 sbyte 而 T2 为 byte、ushort、uint 或 ulong,则 C1 为更好的转换。
       ~如果 T2 为 sbyte 而 T1 为 byte、ushort、uint 或 ulong,则 C2 为更好的转换。
       ~如果 T1 为 short 而 T2 为 ushort、uint 或 ulong,则 C1 为更好的转换。
       ~如果 T2 为 short 而 T1 为 ushort、uint 或 ulong,则 C2 为更好的转换。
       ~如果 T1 为 int 而 T2 为 uint 或 ulong,则 C1 为更好的转换。
       ~如果 T2 为 int 而 T1 为 uint 或 ulong,则 C2 为更好的转换。
       ~如果 T1 为 long 而 T2 为 ulong,则 C1 为更好的转换。
       ~如果 T2 为 long 而 T1 为 ulong,则 C2 为更好的转换。
       ~否则,两个转换都不是更好的转换。

 

    e.函数成员调用(function member invocation)

 

      本段描述在运行时发生的调用一个特定的函数成员的进程。这里假定这个要调用的特定成员,已在编译时进程确定了(可能采用重载决策从一组候选函数成员中选出)。

      为了描述调用进程,将函数成员分成两类:

      静态函数成员。包括实例构造函数、静态方法、静态属性访问器和用户定义的操作符。静态函数成员总是非虚拟的。
      实例函数成员。包括实例方法、实例属性访问器和索引器访问器。实例函数成员不是非虚拟的就是虚拟的,并且总是在特定的实例上调用。该实例由实例表达式计算,并且在函数成员内可以以 this (第 7.5.7 节)的形式对它进行访问。
     

      函数成员调用的运行时处理包括以下步骤(其中 M 是函数成员,如果 M 是实例成员,则 E 是实例表达式):

 

      1'如果 M 是静态函数成员,则: 
         ~它的参数列表按照(2)中的说明进行计算。
         ~调用 M。

 

      2'如果 M 是在值类型中声明的实例函数成员,则: 
         ~计算 E。如果该计算导致异常,则不执行进一步的操作。
         ~如果 E 没有被归类为一个变量,则创建一个与 E 同类型的临时局部变量,并将 E 的值赋给该变量。这样,E 就被重新归类为对该临时局部变量的一个引用。该临时变量在 M 中可以以 this 的形式被访问,但不能以任何其他形式。因此,仅当 E 是真正的变量时,调用方才可能观察到 M 对 this 所做的更改。
         ~参数列表按照(2)中的说明进行计算。
         ~调用 M。E 引用的变量成为 this 引用的变量。

 

      3'如果 M 是在引用类型中声明的实例函数成员,则: 
         ~计算 E。如果该计算导致异常,则不执行进一步的操作。
         ~参数列表按照(2)中的说明进行计算。
         ~如果 E 的类型为值类型,则执行装箱转换以将 E 转换为 object 类型,并且在下列步骤中,E 被视为 object 类型。这种情况下,M 只能是 System.Object 的成员。
检查 E 的值是否有效。如果 E 的值为 null,则引发 System.NullReferenceException,并且不执行进一步的操作。
         ~要调用的函数成员实现按以下规则确定:
如果 E 的编译时类型是接口,则调用的函数成员是 M 的实现,此实现是由 E 引用的实例在运行时所属的类型提供的。确定此函数成员时,应用接口映射规则确定由 E 引用的实例运行时类型提供的 M 实现。否则,

           ~~如果 M 是虚函数成员,则调用的函数成员是由 E 引用的实例运行时类型提供的 M 实现。确定此函数成员时,应用“确定 M 的派生程度最大的实现”的规则(相对于 E 引用的实例的运行时类型)。 否则,

           ~~M 是非虚函数成员,调用的函数成员是 M 本身。

         ~调用在上一步中确定的函数成员实现。E 引用的对象成为 this 引用的对象。 

 

小结:

本篇主要描述了函数成员的基本情况。从函数本身来讲,在普通的CLR环境下,必须通过编译来“喂”给机器去识别,这样的编译模式下,函数仍然无法在运行时动态化,这应该是DLR的用武之地。--运行时,识别环境(context),可配置地动态确定运行时的反应行为。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics