基元类型与FCL类型
编译器直接支持的数据类型称为基元类型。基元类型直接映射到Framework类库(FCL)中存在的类型。例如C#的int直接映射到System.Int32类型。
从另一个角度,可以认为C#编译器自动假定所有源代码文件都添加了以下using指令
using byte=System.Byte using int=System.Int32
基元类型相互转换
虽然int和float是不同的类型,相互不存在派生关系,但是事实上编译和运行都没问题。
因为C#编译器非常熟悉基元类型,会在编译代码时应用自己的特殊规则。编译器能执行基元类型之间的隐式或显式转型。
只有在转换安全的时候,C#才允许隐式转换。所谓安全是指不会发生数据丢失,比如int转换到float。
但如果可能不安全,C#就要求显式转型。不安全意味着转换后可能丢失精度或数量级。例如int转换为byte。
基元类型溢出检查
对基元类型执行的许多算术运算都可能造成溢出。
Byte b=100; b=(Byte)(b+200);//b现在包含44(或者十六进制2C)
可以通过checked和unchecked操作符来提供灵活的溢出检查。
//使用unchecked操作符 int invalid=unchecked((int)(-1));//OK //使用checked操作符 byteb=100; b=checked((byte)(b+200));//抛出OverflowException异常
除了两种操作符,C#还支持checked和unchecked语句,他们使一个块中的所有表达式都进行或不进行溢出检查。
checked{ byte b=100; b+=200;//该表达式会进行溢出检查 }
CLR不将Decimal视为基元类型,如果对Decimal类型使用checked和unchecked操作符,执行的运算是不安全的。
动态类型Dynamic
使用动态类型的需求
C#所有表达式都解析成类型的实例,编译器生成的代码只执行对该类型有效的操作。
即错误在编译时检测到,执行前是正确的。但程序许多时候仍需处理一些运行时才会知晓的信息。
为了方便开发人员使用反射或者与其他组件通信,C#编译器允许将表达式的类型标记为dynamic。还可将表达式的结果放到变量中,并将变量类型标记为dynamic。
运行时决定具体的类型
代码使用dynamic表达式调用成员时,编译器生成特殊的IL代码来描述所需的操作。这种特殊的代码称为有效载荷payload。
在运行时,payload代码根据dynamic表达式、变量引用的对象的实际类型来决定具体执行的操作。
internal static class DynamicDemo{ public static void Main(){ dynamic value; for(int demo=0;demo<2;demo++){ value=(demo==0)?(dynamic)5:(dynamic)"A"; value=value+value; M(value); } } private static void M(Int32 n){Console.WriteLine("M(Int32):"+n);} private static void M(String s){Console.WriteLine("M(String):"+s);} }
执行Main会得到以下输出:
M(int):10 M(string):AA
由于value是dynamic,所以C#编译器生成payload代码在运行时检查value的实际类型,决定+操作符实际要干什么。
dynamic的本质是object
如果字段、方法参数或方法返回值的类型是dynamic,编译器会将该类型转换为System.Object,
并在元数据中向字段、参数、或返回类型应用System.Runtime.CompilerServices.DynamicAttribute的实例。
dynamic类型的局部变量
如果局部变量被指定为dynamic,则变量类型也会成为Object。但不会向局部变量应用DynamicAttribute,因为他限制在方法内部使用。
由于dynamic其实就是Object,所以方法签名不能仅靠dynamic和Object的变化来区分。
泛型代码中不生成有效载荷
泛型类(引用类型)、结构(值类型)、接口、委托或方法的泛型类型实参也可以是dynamic类型。
编译器将dynamic转换成Object,并向必要的各种元数据类型应用DynamicAttribute。
使用的泛型代码是已经编译好的,会将类型视为Object。编译器不在泛型代码中生成payload代码,所以不会执行动态调度。
dynamic类型的隐式转型
所有表达式都能隐式转型为dynamic,因为所有表达式最终都生成从Object派生的类型。
正常情况,编译器不允许写代码将表达式从Object隐式转型为其他类型,必须显式转型。
但是,编译器允许使用隐式转型语法将表达式从dynamic转型为其他类型。
Object o1=123;//OK,从Int32隐式转型为Object,装箱 Int32 n1=o1;//Error,不允许从Object到Int32的隐式转型 Int32 n2=(Int32)o1;//OK,从Object显式转型为Int32,拆箱 dynamic d1=123;//Ok,从Int32隐式转型为dynamic,装箱 In32 n3=d1;//OK,从dynamic隐式转型为Int32,拆箱
从dynamic转型为其他类型时,虽然编译器允许省略显式转型,但CLR会在运行时验证转型来确保类型安全性。
如果对象类型不兼容要转换成的类型,CLR会抛出异常。注意,dynamic表达式的求值结果是一个动态表达式。
dynamic和var
dynamic d=123; var result=M(d);//注意,var result等同于dynamic result
代码之所以能通过编译,是因为编译时不知道调用哪个M方法,从而不知道M的返回类型。
所以编译器假定result变量具有dynamic类型。 如果M方法的返回类型时void,会抛出异常。
不要混淆dynamic和var
var声明局部变量只是一种简化语法,var只能在方法内部声明局部变量,而dynamic可用于局部变量、字段和参数。
表达式不能转型为var,但能转型为dynamic。而且必须显式初始化用var声明的变量,但无需初始化用dynamic声明的变量。
dynamic的限制
dynamic只能访问对象的实例成员,因为dynamic变量必须引用对象。