C#专题之成员查询与函数成员

来源:互联网 发布:java中di是什么意思 编辑:程序博客网 时间:2024/06/08 02:20
作者:思多雅[天行健] 发布时间:2009.1.24
一、 成员查询
成员查询是根据在一个类型的上下文中一个名称的意义是确定的来进行。成员查询会发生在对一个表达式中的简单名称或成员访问进行求值的时候。
类T 中的名称A 的成员查询按下面步骤进行:
*  首先,T 中声明的名为N 的所有可访问成员和T 的基本类型已经被构造。包括override 修饰符的声明被排斥在外。如果没有名为N 的成员存在或可访问,那么查询产生无匹配,而下面的步骤就不再进行。
下面,被其它成员隐藏的成员被从集合中除去,对于集合中任何一个成员S.M,使用下面的规则: (这里在类型S 中,成员M 被声明)
*  如果M 是一个常数、域、属性、事件、类型或枚举成员,那么所有在S 的基类中声明的成员都要被从集合中去掉。
   *  如果M 是一个方法,那么S 的一个基本类型中声明的所有非方法成员都要从这个集合中去掉,
      并且S 的基本类型中声明的所有与M 有相同签名的方法都要从这个集合中去掉。
*  最后,把隐含成员去掉后,查询的结果就确定了:
*  如果集合是由单个非方法成员构成,那么这个成员就是查询的结果。
*  另外,如果集合中只包含方法,那么这个方法组就是查询的结果。
*  另外,如果查询是不明确的,就会产生编译时错误 (这个情况只能发生在一个有多个直接基本接口的接口的成员查询中)。
对于不同于接口的类型中的成员查询和严格单继承的接口中的成员查询(继承链中的每个接口没有或只有一个直接基接口),成员查询规则的作用是简单的,派生的成员隐藏了有相同名称或签名的基本成员。这样的单继承查询不会是不明确的。不明确的查询只可能在多继承接口成员查询时发生。
1.1 基类型
出于成员查询的目的,类型T 可以被考虑有下面的基类型:
*  如果T 是对象(object)类型,那么T 没有基类型。
*  如果T 是一个数值类型,那么T 的基类型是类类型对象(object)。
*  如果T 是一个类类型,那么T 的基类型是T 的基类,包括类类型对象(object)。
*  如果T 是一个接口类型,那么T 的基类型是T 的基接口和类类型对象(object)。
*  如果T 是一个数组类型,那么T 的基类型就是类类型System.Array和对象(object)。
*  如果T 是一个代表类型,那么T 的基类型就是类类型System.Delegate和对象(object)。

-------思多雅[天行健]版权所有,首发太平洋论论坛,转载请注明-------

二、函数成员
函数成员是一些包含可执行语句的成员。函数成员总是类型的成员,并且不能是名称空间的成员。C#定义了下面五种函数成员:
*  构造函数
*  方法
*  属性
*  索引
*  用户定义操作符
在函数成员中包含的语句通过函数成员调用来执行。函数成员调用的事件语法是根据不同的函数成员种类来定。然而,所有的函数成员调用都是表达式,它允许参数被传送到函数成员中,并且允许函数成员计算和返回一个结果。
一个函数成员调用的参数列表提供了函数成员实际数值或的对参数引用的变量。
构造函数、方法、索引和操作符的引用,使用重载分析来确定调用哪个候选功能参数集。
一旦在编译时一个详细的函数成员被确定,可能会通过重载分析。
下面的表格总结了在构造五种函数成员发生的过程。在表中,e、x、y和value指示表达式被作作为变量或数值分类,T 表示一个作为类型分类的表达式,F是一个方法的简单名称,P是一个属性的简单名称。
结构 例子 描述
构造函数调用 newT(x,y) 重载分析被应用于在类或结构T 中选择最好的构造函数。 构造函数被调用,有参数列表(x,y)。
方法调用 F(x,y) 重载分析被应用于在包含的类或结构中选择最好的方法                             F。方法调用有参数列表(x,y)。如果方法不是static,实例表达式就是this。
T.F(x,y) 重载分析被应用于在类或结构T 中选择方法F。如果方法不是static,会发生错误,方法调用有参数列表(x,y)。
e.F(x,y) 重载分析被应用于在e 给定的类、结构或接口中选择方法F。如果方法不是static,会发生错误,方法调用有参数列表(x,y)。
属性访问 P 在包含类或结构中的属性的get 访问符被调用。如果P 是                             只写的,会产生错误,如果P 不是静态的,实例表达式就是this。
P=value 在包含类或结构中的属性P 的set 访问符被调用,并且有参数列表(value)。如果P 是只读的,会产生错误,如果P不是静态的,实例表达式就是this。
T.P 在类或结构T 中的属性的get 访问符被调用。如果P 是只写的,会产生错误,如果P 不是静态的,实例表达式就是this。
T.P =value 在类或结构T 中的属性P 的set 访问符被调用,并且有参数列表(value)。如果P 是只读的,会产生错误,如果P不是静态的,实例表达式就是this。
e.P 在类型e 提供的类、结构或接口中的属性的get 访问符被调用。如果P 是只写的,会产生错误。
e.P=value 在类型e 提供的类、结构或接口中的属性P 的set 访问符被调用,并且有参数列表 (value)。如果P 是只读的,会产生错误。
索引访问 e[x,y] 重载分析被用于选择类型e 提供的类、结构或接口。索引的get 访问符被调用,有实例表达式e 和参数列表(x,y)。如果P 是只写的,会产生错误。
e[x,y]=value 重载分析被用于选择类型e 提供的类、结构或接口。索引的set 访问符被调用,有实例表达式e 和参数列表(x,y, value)。如果P 是只读的,会产生错误。
操作符调用 -x 重载分析被用于在类型x 所给的类或结构中选择最好的一元操作符。所选择的操作符被调用,有参数列表 (x )。
x+y 重载分析被用于在类型x 和y 所给的类或结构中选择最好的一元操作符。所选择的操作符被调用,有参数列表(x, y)。
2.1 参数列表
每种函数成员调用都包括一个参数列表,它提供了对函数成员的参数的引用的真实数据或变量。指定函数成员的参数列表的语法要根据函数成员的种类:
*  对于构造函数、方法和代表,参数被指定为一个参数列表,如下面所描述的一样。
*  对于属性,当调用get 访问符时参数列表是空的,并且当调用set 访问符时由所指定的复制操作符的右边操作数构成。
*  对于标签,参数列表由标签访问中在方括号中指定的表达式构成。当调用set 访问符时,参数列表另外包括所指定的复制操作符的右边操作数。
*  对于用户定义的操作符,参数列表由一元操作符的单操作数或二元操作符的两个操作数构成。
属性、索引和用户定义的操作符的复制通常要传递数值参数。这些函数成员的种类不支持引用和输出参数。
一个构造函数、方法或代表调用的参数按一个参数列表指定:
      argument-list:
        argument
        argument-list , argument
      argument:
        expression
         ref variable-ref erence
        out variable-ref erence
一个参数列表由零或者更多的参数组成,用逗号隔开。每个参数可以使用下面方法中的一种:
*  一个表达式,表示参数作为数值参数传递。
*  关键词ref之后加一个变量引用 (§错误!未找到引用源。),指出参数作为引用参数传送。
一个变量必须在它能作为一个引用参数传送前被赋值。
*  关键词out 之后加有一个变量引用 (§错误!未找到引用源。),指出参数作为输出参数传送。一个变量必须在它能作为一个引用参数传送前被赋值。
在运行函数成员调用过程 (§7.4.3)期间,引用一个参数列表的表达式被按顺序求值,从左到右,如下所示:
*  对于一个数值参数,参数表达式被求值并且隐式的转换(§6.1)为相应的参数类型。结果数据变为函数成员调用中数值参数的初始值。
*  对于一个引用或输出参数,变量引用被求值,并且结果存储的位置变为在函数成员调用中的参数所表示的存储位置。如果变量引用为一个引用或输出参数是一个引用类型的数组元素,就会有一个运行时检查,来使得那个数组的元素类型与参数的类型是一致的。如果检查失败,就会抛出ArrayTypeMismatchException信息。
一个参数列表的表达式通常按他们书写的顺序求值。
这样,来看看这个例子:
      class Test
      {
          static void F(int x, int y, int z) {
             Console.WriteLine("x = {0}, y = {1}, z = {2}", x, y, z);
          }
          static void Main() {
             int i = 0;
             F(i++, i++, i++);
          }
      }
产出输出
      x = 0, y = 1, z = 2
数组的共同变化规则 (§12.5)允许一个数组类型A[]的数值变为对一个数组类型B[]的实例的引用,提供了一个从B到A 的一个隐式引用转换。因为这项规则,当一个引用类型的数组元素被作为一个引用或输出参数传递时,需要进行运时检查来数组的实际元素与那个参数的相同。在例子中
      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
          }
      }
第二个对F 的引用使得抛出了一个ArrayTypeMismatchException信息,因为b 的实际元素类型是字符串而不是对象。
2.2 重载分析
重载分析是一种用来从参数列表和候选函数成员集中选择最好的函数成员来调用的机制。在下面的关于C#的文字中,重载分析选择函数成员来调用:
*  方法的调用,在一个调用表达式中命名(§7.5.5)。
*  构造函数的调用,在对象创建表达式中命名 (§7.5.10.1)。
*  索引访问符的调用通过一个元素访问(§7.5.6)。
*  预定义或用户定义操作符的调用,在表达式中引用。
这些上下文的每一个都以自己的方法定义了一个候选函数成员集和参数列表。然而,一旦候选函数成员和参数列表被确定,对于最优函数成员的选择在所有情况下都是相同的:
*  首先,候选函数成员集被简化到只包括用类提供参数列表(§7.4.2.1)的函数成员。如果被简化的集合是空的,就会产生错误。
*  这样,所给出的可用候选函数成员,而集合中的最优函数成员被确定了。如果集合中只包含一个函数成员,那么这个函数成员就是最优函数成员。另外,最优函数成员是一个在考虑所给的参数列表时比其它所有函数成员都要好的函数成员,通过使用规则把每个函数成员于其它所有函数成员比较得出。如果这里不只是一个函数成员比其它所有的函数成员要好,那么函数成员调用就是不确定的并且会产生错误。
下面我们来看看可使用函数成员和更好的函数成员的确切含义。
2.2.1 可使用函数成员
一个函数成员在下面所有都为真的时候,就被认为是考虑参数列表的可使用函数成员:
*   A 中的参数数量是于函数成员声明中的参数数量相同。
*   对于A 中的每个参数,参数的传输模式与相应的参数的参数[这里可以注意哦]传输模式是相同的,并且来说,对于一个输入参数,从参数类型到相应的参数类型的隐式的转换是存在的,或者是使用对于一个ref或out参数的,参数的类型被指定为相应参数的类型。
2.2.2 更好的函数成员
给出一个参数列表A,其中有参数类型集A , A , ..., A 和两个有参数类型P , P , ..., P 和Q , Q , ..., Q 的可使用函数成员Mp 和Mq ,如果Mp 定义为比Mq 更好的函数成员
*   对于每个参数,从Ax 到Px 的隐式转换不会比从Ax 到Qx 的隐式转换更糟,并且
*   至少有一个参数,从Ax 到Px 的转换比从Ax 到Qx 的转换要好。
2.2.3 更好的转换
给出一个隐式转换C1 ,它从类型S 转换为类型T1 ,和一个隐式转换C2 它从类型S 转换到类型T2 ,两个转换中更好的转换由下面决定:
*   如果T1 和T2 是相同的类型,没有哪种转换更好。
*   如果S 是T1 ,C1 是更好的转换。
*   如果S 是T2 ,C2 是更好的转换。
*   如果从T1 到T2 的隐式转换存在,但没有从T2 到T 1的隐式转换存在,那么C1是更好的转换。
*   如果从T2 到T1 的隐式转换存在,但没有从T1 到T2 的隐式转换存在,那么C1 是更好的转换。
*   如果T1 是sbyte而T2 是byte、ushort、uint或ulong,C1 是更好的转换。
*   如果T2 是sbyte而T 1 是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 是更好的转换。
另外,没有转换当然是更好的。
如果按这种规则定义的隐式转换C1 是比隐式转换C2 更好的转换,那么也有C2 比C1 更差的情况。

2.3 功能成员调用
这节中描述在运行时调用一个特殊的功能成员发生的过程。假设一个编译时的过程已经决定要调用的特殊成员,很可能是通过对候选功能成员集使用重载分析来实现。
出于描述引用过程的目的,函数成员被分为两种:
*  静态函数成员。这里有静态方法、构造函数、静态属性访问符和用户定义的操作符。静态函数成员通常是非虚的。
*  实例函数成员。这里有实例方法、实例属性访问符和索引访问符。实例函数成员可以是非虚拟或虚拟,并且通常是在一共特殊实例中引用。这个实例由一个实例表达式计算,并且它在功能成员中变的可访问,就像this。
函数成员的运行时过程由下面的步骤组成,这里M 是函数成员,而如果M 是一个实例成员,E 是实例表达式:
*  如果M 是一共静态函数成员:
*  参数列表就像§7.4.1中描述一样被求值。
*  M被调用。
*  如果M 是一个在数值变量中声明的实例函数成员:
*  E被求值。如果这个求值引起了一个例外,那么就不需要执行以后的步骤。
*  如果E 没有被划分为变量,那么E 类型的暂时局部变量将被创建,并且E 的数值分配给那个变量。于是E 被重新分类为对那个暂时局部变量的引用。这个暂时变量是可访问的就像M 中的 this,但是不能用其它任何方法。这样,只有当E 是一个真正的变量时,调用者才有可能观察M对this 所做的改变。
*  参数列表就像§7.4.1中描述一样被求值。
*  M被调用。被E 引用的变量变为被this 引用的变量。
*  如果M 是一个在引用类型中声明的实例函数成员:
   *  E被求值。如果这个求值引起了一个例外,那么就不需要执行以后的步骤。
   *  参数列表就像§7.4.1中描述一样被求值。
   *  E 的类型是数值类型,一个打包转换(§错误!未找到引用源。)被实现来把E 转换为object 类型,而E 在后面的步骤中被认为是object 类型。
   *  E 的值被检查为有效的。如果E 的数值为null,就会抛出一个NullReferenceException例外,而后面的步骤将不会执行。
   *  用来调用的函数成员执行由下面决定:如果M 是一个非虚拟的函数成员,那么M 是用来调用的函数成员执行。另外,M 是一个虚拟函数成员而用来调用的函数成员执行被通过虚拟函数成员查询或接口函数成员查询)来决定。
   *  上面步骤中确定的函数成员执行被调用。被E 引用的对象变为被this 引用的对象。
2.3.1 被包装实例的调用
在下面的情况中,一个以数值类型被执行的函数成员,可以通过那个数值类型的被包装实例来引用:
*   当函数成员是一个从类型object 继承的方法的替代,并且是通过一个类型object  的实例表达式调用的时。
*   当函数成员是一个接口函数成员的执行,并且是通过一个接口类型的实例表达式调用的时。
*   当函数成员通过一个代表被调用时。
在这些情况中,被包装实例被认为是包含了数值类型的变量,并且这种变量变成函数成员调用中被this引用的变量。这特别说明当一个函数成员在被包装实例中被调用时,就允许函数成员修改包含在被包装实例中的数值。
原创粉丝点击