CLR
小结:
1. CLR 执行模型(略举3个托管模块,3个Resource files)
2. 托管模块(Managed Module)
是一个32位或64位的可移植执行文件,主要由IL和Metadata组成。其中Metadata是一系列数据表格,其中一些表格是用来记录该Module中定义的类型和成员,另外还有些数据表是用来记录该Module中引用的外界类型及成员
3. CLR核心功能
a. 内存管理
b. 程序集的加载
c. 异常的处理
d. 线程同步
e. 安全问题:如类型安全,不同的类型之间不能随意的进行转换,在Runtime时,CLR会作严格的检查,CLR允许同类型互转以及子类转成其父类
1)子类转父类
不管是隐式转还是显式转,编译时期【Compile-time】与运行时期【Runtime】都是可以通过的
2)父类转子类
必须显式转【强制类型转换】,不然在编译时期【Compile-time】直接报错,且转换的结果可能失败,在运行时期【Runtime】CLR还会做进一步的检查。
3) is 和 as
eg1:【设有Object类型和Employee类型】
Object o = new Object();
if(o is Employee) // CLR会检查o变量引用的类型是否与Employee类型兼容(必须引用的是Employee类型或Employee类型的子类)
{
Employee e = (Employee)o;// CLR会再次检查o变量引用的具体类型
}
---对于is运算,CLR会检查两次,带来了性能上的开销,基于此,C#提供了一个更简单且性能更好的as运算符
Employee e = o as Employee;//直接做转换,CLR只检查一次
if(e !=null )
{
//...
}
---这两个运算符在转换失败时都不会抛出异常,is成功时返回true, 失败时返回false; as成功时返回非Null, 失败时返回null
4. CTS【Common Type System】公共类型系统
描述类型的定义以及它们的表现
a. CTS规定一个类型可以含0个或多个成员(域,方法,属性,事件)
b. CTS定义了类型可见和成员访问规则(private, protected, internal protected, public)
5. CLS【Common Language Specification】
6. 在用new关键字新建一个对象时,CLR做了哪些事
eg: Employee e = new Employee("Param1");
a. 统计Employee类型中定义的成例成员所需的字节数 + 从继承链上端继承下来的实例成员所需字节数 + 在托管堆上分配对象时所需的类型对象指针
+ 在托管堆上分配对象时所需的同步块索引【统计总共所需的字节数】
b. 在托管堆上分配空间
c. 初始化类型对象指针和同步块索引
d. 实例构造函数调用顺序:
本类静态成员变量初始化--》本类静态构造函数--》本类实例成员变量初始化--》父类静态成员变量初始化--》父类静态构造函数--》父类实例成员变量初始化--》父类实例构造函数--》本类实例构造函数
测试代码如下:
public class BaseA { public static MyTest a1 = new MyTest("a1"); public MyTest a2 = new MyTest("a2"); static BaseA() { MyTest a3 = new MyTest("a3"); } public BaseA() { MyTest a4 = new MyTest("a4"); } public virtual void MyFun() { MyTest a5 = new MyTest("a5"); } //形成的IL Code 如下 //static BaseA() //{ // a1 = new MyTest("a1"); // MyTest a3 = new MyTest("a3"); //} //public BaseA() //{ // this.a2 = new MyTest("a2"); // MyTest a4 = new MyTest("a4"); //} } public class BaseB : BaseA { public static MyTest b1 = new MyTest("b1"); public MyTest b2 = new MyTest("b2"); static BaseB() { MyTest b3 = new MyTest("b3"); } public BaseB() { MyTest b4 = new MyTest("b4"); } public new void MyFun() { MyTest b5 = new MyTest("b5"); } //形成的IL Code 如下 //static BaseB() //{ // b1 = new MyTest("b1"); // MyTest b3 = new MyTest("b3"); //} //public BaseB() //{ // this.b2 = new MyTest("b2"); // MyTest b4 = new MyTest("b4"); //} } class Program { static void Main(string[] args) { BaseB baseb = new BaseB(); baseb.MyFun(); Console.ReadKey(); } } public class MyTest { public MyTest(string info) { Console.WriteLine(info); } }
//输出结果为:b1,b3,b2,a1,a3,a2,a4,b4,b5
----有一点还得注意,就是关于本类或父类中的实例变量初始化的代码写的位置会影响代码调用顺序,如上面的BaseA类中:
public MyTest a2 = new MyTest("a2");
public BaseA()
{
MyTest a4 = new MyTest("a4");
}
实例成员变量MyTest a2 初始化是写在构造函数BaseA()外面的,
若把它写在构造函数里面,如下:
public MyTest a2;
public BaseA()
{
a2 = new MyTest("a2");
MyTest a4 = new MyTest("a4");
}
虽然这两种写法产生的IL CODE 是一样的,如下
public BaseA()
{
this.a2 = new MyTest("a2");
MyTest a4 = new MyTest("a4");
}
但是,调用顺序却发生了必变,放在外面的话,会先执行MyTest(string)构造函数,然后再去调用父类中的静态构造函数,实例构造函数,再返回本类构造函数中运行其他代码
放在里面的话,则不会先执行MyTest(string)构造函数,只有到执行本类构造函数中的代码时才会运行
e. 返回一个新建对象的引用