表达式是一个运算符和操作数的序列。
一、表达式的分类。
- 值:每个值都有关联的类型。
- 变量:每个变量都有关联的类型,称为该变量的已声明类型。
- 命名空间:只能出现再member-access的左侧。
- 类型:只能出现在member-access的左侧,或作为as运算符、is运算符、typeof运算符的操作数。
- 方法组:它是一组重载方法,是成员查找的结果。方法组可能具有关联的实例表达式和关联的类型实参列表。当调用实例方法时,实例表达式的计算结果成为由this表示的实例。
- null文本:归类为null文本的表达式可以隐式转换为引用类型或可以为null的类型。
- 匿名函数:可以隐式转换为兼容的委托类型或表达式目录树类型。
- 属性访问:每个属性访问都有关联的类型,即该属性的类型。属性访问可以有关联的实例表达式。当调用get或set的实例属性访问器时,实例表达式的计算结果将成为this表示的实例。
- 事件访问:每个事件访问都有关联的类型,即该事件的类型。此外,事件访问还可以有关联的实例表达式。事件访问可作为+=和-=的左操作数出现。
- 索引器访问:每个索引器访问都有关联的类型,即该索引器的元素类型。此外,索引器访问还可以有关联的实例表达式和关联的参数列表。当调用索引器访问的访问器(get或set块)时,实例表达式的计算结果将成为由this表示的实例,而实参列表的计算结果将成为调用的形参列表。
- Nothing:这出现在表达式是调用一个具有void返回值类型的方法时。
二、静态绑定和动态绑定
绑定:根据构成表达式(参数、操作数、接收器)的类型或值确定操作含义的过程。
静态绑定:操作的含义通常在编译时根据其构成表达式的编译时类型确定。
动态绑定:操作的绑定推迟到要在程序运行过程中执行此操作的时间,它所参与的任何邦迪都应基于其运行时类型(即它在运行时所表示的对象的实际类型)。当操作为动态绑定时,编译器只执行很少检查或根本不执行检查,遇到绑定失败时错误将在运行时报告为异常。
三、运算符
一元运算符:一元运算符带一个操作数并使用前缀表示法(++x)或后缀表示法(x++)。
二元运算符:二元运算符带两个操作数并且全部使用中缀表示法(x + y)。
三元运算符:只存在一个三元运算符?:他带着三个操作数并使用中缀表示法(z ? x : y )。
当表达式包含多个运算符时,运算的优先级(precedence)控制个运算符的计算顺序。下表按照从最高到最低的优先级顺序概括了所有的运算符:
类别 | 运算符 |
基本 | x.y f(x) a[x] x++ x-- new typeof default checked unchecked delegate |
一元 | + - ! ~ ++x --x (T)x await x |
乘法 | * / % |
加减 | + - |
移位 | << >> |
关系和类型检测 | < > <= >= is as |
相等 | == != |
逻辑AND | & |
逻辑XOR | ^ |
逻辑OR | | |
条件AND | && |
条件OR | || |
null合并 | ?? |
条件 | ?: |
赋值和lambda表达式 | = *= /= %= += -= <<= >>= &= ^= |= => |
当操作数出现在具有相同优先级的两个运算符之间时,优先级和顺序关联性都可以用括号控制。运算符的顺序关联性控制运算的执行顺序:
- 除赋值运算符和null合并运算符外,所有二元运算符均为左结合,表示从左向右执行运算。
- 赋值运算符、null合并运算符和条件运算符(?:)为右结合,表示从右向左执行运算。
数值提升
数值提升包括自动为预定义一元和二元数值运算符的操作数执行某些隐式转换。数值提升不是一个独特的机制,而是一种将重载决策应用于预定义运算符所产生的效果,数值提升尤其不影响用户定义运算符的计算。
一元数值提升:是针对预定义的+、-和~一元运算符的操作数发生的。一元数值提升仅包括将sbyte、byte、short、ushort或char类型的操作数转换为int类型。此外,对于一元运算符,一元数值提升将uint类型的操作数转换为long类型。
二元数值提升:是针对预定义的+、-、*、/、%、&、|、^、==、!=、>、<、>=、和<=二元运算符操作数发生的。二元数值提升隐式地将两个操作数都转换为一个公共类型,如果涉及的是非关系运算符,则此公共类型还成为运算的结果类型。二元数值提升应按下列规则进行:
- 如果有一个操作数的类型为decimal,则另一个操作数转换为decimal类型;否则,如果另一个操作数的类型为float或double,则发生绑定时错误。
- 否则,如果有一个操作数的类型为double,则另一个操作数转换double类型。
- 否则,如果有一个操作数的类型为float,则另一个操作数转换为float类型。
- 否则,如果有一个操作数的类型为ulong,则另一个操作数将转换为ulong类型;否则,如果另一个操作数的类型为sbyte、short、int或long,则将发生绑定时错误。
- 否则,如果有一个操作数的类型为long,则另一个操作数类型转换为long类型。
- 否则,如果有一个操作数的类型为uint,而另一个操作数的类型为sbyte、short或int,则两个操作数均将转换为long类型。
- 否则,如果有一个操作数的类型为uint,则另一个操作数转换为uint类型。
- 否则,两个操作数都转换为int类型。
注:第一个规则不允许将decimal类型于double和float类型混用,两种类型不存在隐式转换。
当一个操作数为有符号的整型时,另一个操作数的类型不可能为ulong类型。
四、转换
转换分为隐式转换和显式转换;
隐式转换:
- 标识转换
标识转换是在同一类型内进行转换。这种转换的存在,是为了使已具有所需的类型的实体可被认为是可转换的。
- 隐式数值转换
从 sbyte 到 short、int、long、float、double 或 decimal。
从 byte 到 short、ushort、int、uint、long、ulong、float、double 或 decimal。
从 short 到 int、long、float、double 或 decimal。
从 ushort 到 int、uint、long、ulong、float、double 或 decimal。
从 int 到 long、float、double 或 decimal。
从 uint 到 long、ulong、float、double 或 decimal。
从 long 到 float、double 或 decimal。
从 ulong 到 float、double 或 decimal。
从 char 到 ushort、int、uint、long、ulong、float、double 或 decimal。
从 float 到 double。
- 隐式枚举转换
隐式枚举转换允许将十进制整数文字(decimal-integer-literal)转换为任何枚举类型(enum-type)以及任何基础类型为枚举类型(enum-type)的可null类型(nullable-type)。
- 可以为null的隐式转换
对不可以为null的值类型执行预定义隐式转换也可用于这些类型的可以为null的形式,对于每种从不可以为null的值类型S转换为不可以为null的值类型T的预定义隐式标识和数值转换,都存在如下可以为null的隐式转换:
从S?到T?的隐式转换。
从S到T?的隐式转换。
基于从S到T的基础类型转换来计算可以为null的隐式转换如下进行:
如果可以为null的转换是从S?到T?:
如果源值为null(hasvalue属性为false),则结果为T?类型的null值。
否则,转换计算过程为从S?解包为S,然后进行从S到T的基础转换,最后从T包装为T?。
如果可以为null的转换是从S到T?,则转换计算过程为从S到T的基础类型转换,然后从T包装为T?。
- null文本转换
从null文本到任何可以为null的类型存在隐式转换。这种转换产生可以为null的给定类型的null值。
- 隐式引用转换
隐式引用转换是指reference-type之间的转换,可以证明这些转换总能成功,因此不需要再运行时进行任何检查,引用转换无论是隐式还是显式的,都不会改变被转换的对象的引用标识。换而言之,虽然引用转换可能更改引用的类型,但绝不会更改所引用对象的类型或值。
从任何 reference-type 到 object 和 dynamic。
从任何 class-type S 到任何 class-type T(前提是 S 是从 T 派生的)。
从任何 class-type S 到任何 interface-type T(前提是 S 实现了 T)。
从任何 interface-type S 到任何 interface-type T(前提是 S 是从 T 派生的)。
从元素类型为 SE 的 array-type S 到元素类型为 TE 的 array-type T(前提是以下所列条件均成立):
S 和 T 只有元素类型不同。换言之,S 和 T 具有相同的维数。
SE 和 TE 都是 reference-type。
存在从 SE 到 TE 的隐式引用转换。
从任何 array-type 到 System.Array 及其实现的接口。
从一维数组类型 S[] 到 System.Collections.Generic.IList<T> 及其基接口(前提是存在从 S 到 T 的隐式标识或引用转换)。
从任何 delegate-type 到 System.Delegate 及其实现的接口。
从 null 文本到任何 reference-type。
从任何 reference-type 到 reference-type T (前提是它具有到 reference-type T0 的隐式标识或引用转换,且 T0 具有到 T 的标识转换)。
从任何 reference-type到接口或委托类型 T(前提是它具有到接口或委托类型 T0 的隐式标识或引用转换,且 T0 可变化转换为T)。
- 装箱转换
装箱转换允许将值类型(value-type)隐式转换为引用类型。存在从任何不可空值类型(non-nullable-value-type)到object和dynamic、System.ValueType以及到不可空值类型(non-nullable-value-type)实现的任何接口类型(interface-type)的装箱转换。
此外,枚举类型(enum-type) 还可以转换为 枚举类(System.Enum)类型。
存在从可空类型(nullable-type)到 不可空值类型(non-nullable-value-type)到该引用类型的装箱转换。
如果值类型具有到接口类型 I0 的装箱转换,且 I0 具有到接口类型 I 的标识转换,则值类型具有到 I 的装箱转换。
如果值类型具有到接口或委托类型 I0 的装箱转换,且 I0 变化转换为接口类型 I,则值类型具有到 I 的装箱转换。
将 不可空值类型(non-nullable-value-type) 的值装箱包括以下操作:分配一个对象实例,然后将 值类型(value-type) 的值复制到该实例中。结构可装箱为类型 System.ValueType,因为该类型是所有结构的基类。
nullable-type 的值的装箱的过程如下:
如果源值为 null(HasValue 属性为 false),则结果为目标类型的 null 引用。
否则,结果为对经过源值解包和装箱后所产生的装箱 T 的引用。
- 隐式动态转换
存在从 dynamic 类型的表达式到任何类型 T 的隐式动态转换。转换是动态绑定(第 7.2.2 节),这意味着会在运行时看到从表达式的运行时类型到 T 的隐式转换。如果未发现任何转换,则会引发运行时异常。
- 隐式常量表达式转换
隐式常量表达式转换允许进行以下转换:
int 类型的常量表达式(constant-expression)可以转换为 sbyte、byte、short、ushort、uint 或 ulong 类型(前提是 constant-expression 的值在目标类型的范围之内)。
long 类型的 常量表达式(constant-expression)可以转换为 ulong 类型(前提是 constant-expression 的值非负)。
- 用户定义的隐式转换
用户定义的隐式转换由以下三部分组成:先是一个标准的隐式转换(可选);然后是执行用户定义的隐式转换运算符;最后是另一个标准的隐式转换(可选)。
- 匿名函数转换
- 方法组转换
匿名函数和方法组本身没有类型,但可以隐式转换为委托类型或表达式树类型。
隐式转换可以在多种情况下发生,包括函数成员调用、强制转换表达式和赋值。
预定义的隐式转换总是会成功,从来不会导致引发异常。
显示转换:
- 所有隐式转换。
- 显式数值转换。
从 sbyte 到 byte、ushort、uint、ulong 或 char。
从 byte 到 sbyte 和 char。
从 short 到 sbyte、byte、ushort、uint、ulong 或 char。
从 ushort 到 sbyte、byte、short 或 char。
从 int 到 sbyte、byte、short、ushort、uint、ulong 或 char。
从 uint 到 sbyte、byte、short、ushort、int 或 char。
从 long 到 sbyte、byte、short、ushort、int、uint、ulong 或 char。
从 ulong 到 sbyte、byte、short、ushort、int、uint、long 或 char。
从 char 到 sbyte、byte 或 short。
从 float 到 sbyte、byte、short、ushort、int、uint、long、ulong、char 或 decimal。
从 double 到 sbyte、byte、short、ushort、int、uint、long、ulong、char、float 或 decimal。
从 decimal 到 sbyte、byte、short、ushort、int、uint、long、ulong、char、float 或 double。
显式数值转换有可能丢失信息或导致引发异常:
- 对于一个从整型到另一个整型的转换,处理取决于该转换发生时的溢出检查:
- 在checked上下文中,如果源操作数的值在目标类型的范围之内,就会转换成,但如果源操作数的值在目标类型的范围外,则会引发System.overflowException。
- 在unchecked上下文中,转换总是会成功并按下面规则继续:
- 如果源类型大于目标类型,则截断源值(截去源值中容不下的最高有效位)。然后将结果视为目标类型的值。
- 如果源类型小于目标类型,则源值或按符号扩展或按零扩展,以使它的大小与目标类型相同。如果源类型是有符号的,则使用按符号扩展;如果源类型是无符号的,则使用按零扩展。然后将结果视为目标类型的值。
- 如果源类型的大小与目标类型相同,则源值被视为目标类型的值。
- 对于从decimal到整型的转换,源值向零舍入到最近的整数值,改正数之成为转换的结果。如果得到的整数值在目标类型范围之外,则会引发System.overflowException。
- 对于从float或double到整型的转换,处理取决于发生该转换时的溢出检查:
- 在checked上下文中:如果操作数的值NaN(not a number)或无穷大,则将引发System.overflowException。否则,源操作数会向零舍入到最接近的整数值。如果该整数值处于目标类型范围内,则该值就是转换的结果。否则将引发System.overflowException。
- 在unchecked上下文中:如果操作数的值是NaN(not a number)或无穷大(infinite),则转换的结果是目标类型的一个未经指定的值。否则源操作数会向零舍入到最接近的整数值。如果该整数值处于目标类型的范围内,则该值就是转换的结果。否则,转换的结果是一个目标类型未经指定的值。
- 对于从double到float的转换,double值将舍入为最接近的float值。如果double值过小,无法表示为float值,则结果为0,如果double值过大,则结果为正无穷大。如果double为NaN,结果也为NaN。
- 对于从float或doubl到decimal的转换,源值将转换为decimal表示形式,并且在需要时,将它在第28位小数位上舍入到最接近的数字。如果源值过小,无法表示为decimal,则结果为0。如果源值为NaN、无穷大或者太大而无法表示为decimal,将引发System.overflowException。
- 对于从decimal到float或double的转换,decimal值将舍入为最接近的double或float值。此过程可能会丢失精度,但是绝不会引发异常。
- 显式枚举转换。
从 sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double 或 decimal 到任何 enum-type。
从任何 enum-type 到 sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double 或 decimal。
从任何 enum-type 到任何其他 enum-type。
两种类型之间的显式枚举转换是通过将任何参与的 enum-type 都按该 enum-type 的基础类型处理,然后在产生的类型之间执行隐式或显式数值转换进行的。
- 可以为 null 的显式转换。
可以为null的显式转换允许将对不可以为null的值类型执行的预定义显式转换也用于这些类型的可以为null的形式,对于从不可以为null的值类型S转换为不可以为null的值类型T的每一张预定义显式转换,都存在以下可以为null的转换:
- 从S?到T?的显式转换。
- 从S到T?的显式转换。
- 从S?到T的显式转换。
- 显式引用转换。
- 从 object 和 dynamic 到任何其他 reference-type。
- 从任何 class-type S 到任何 class-type T(前提是 S 为 T 的基类)。
- 从任何 class-type S 到任何 interface-type T(前提是 S 未密封并且 S 未实现 T)。
- 从任何 interface-type S 到任何 class-type T(前提是 T 未密封或 T 实现 S)。
- 从任何 interface-type S 到任何 interface-type T(前提是 S 不是从 T 派生的)。
- 从元素类型为 SE 的 array-type S 到元素类型为 TE 的 array-type T(前提是以下所列条件均成立):
- S 和 T 只有元素类型不同。换言之,S 和 T 具有相同的维数。
- SE 和 TE 都是 reference-type。
- 存在从 SE 到 TE 的显式引用转换。
- 从 System.Array 及其实现的接口到任何 array-type。
- 从一维数组类型 S[] 到 System.Collections.Generic.IList<T> 及其基接口(前提是存在从 S 到 T 的显式标识或引用转换)。
- 从 System.Collections.Generic.IList<S> 及其基接口到一维数组类型 T[](前提是存在从 S 到 T 的显式标识或引用转换)。
- 从 System.Delegate 及其实现的接口到任何 delegate-type。
- 从引用类型到引用类型 T(前提是它具有到引用类型 T0 的显式引用转换,且 T0 具有到 T 的标识转换)。
- 从接口类型到接口或委托类型 T(前提是它具有到接口或委托类型 T0 的显式引用转换,且 T0 可变化转换为 T,或 T 可变化转换为 T0(第 13.1.3.2 节))。
- 从 D<S1…Sn> 到 D<T1…Tn>,其中 D<X1…Xn> 是泛型委托类型,D<S1…Sn> 与 D<T1…Tn> 不兼容或不相同,并且,对于 D 的每个类型形参 Xi,存在以下情况:
- 如果 Xi 是固定的,则 Si 与 Ti 相同。
- 如果 Xi 是协变的,且存在从 Si 到 Ti 的隐式或显式标识或引用转换。
- 如果 Xi 是逆变的,则 Si 与 Ti 相同或同为引用类型。
- 涉及已知为引用类型的类型形参的显式转换。
显式引用转换是那些需要运行时检查以确保他们正确的引用类型之间的转换。
- 显式接口转换。
- 拆箱转换。
拆箱转换允许将引用类型显式转换为值类型(value-type)。
- 显式动态转换
存在从dynamic类型的表达式到任何类型T的显式动态转换。
- 用户定义的显式转换。
三部分组成:先是一个标准的显式转换(可选),然后式执行用户定义的隐式或显式转换运算符,最后式另一个标准的显式转换(可选)。