CLR via C#-基元类型

基元类型与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变量必须引用对象。

原文地址:https://www.cnblogs.com/errornull/p/9743295.html