1. CLR是一个更好的COM。从COM开发人员所面临的问题入手,讲述了CLR如何应用虚拟化以及无所不在的、可扩展的元数据,解决这些COM问题,从而最终取代COM。
2. 组件。从根本上讲,CLR是OS和COM加载器的替代品,本章讨论了代码是如何封装和加载的,它们与Win32和COM世界中的情形大相径庭。
3. 类型基础。组件是代码和构成f类型定义的元数据的容器。本章重点讨论了通用类型系统(Common Type System, CTS),包括类型的组成部分,以及类型是如何关联的。
完全限定的类型包括3个部分:程序集名字、可选的命名空间前缀和类型名称。类型可以包括3类成员:字段、方法和嵌套类型。
类型的字段控制内存如何分配,CLR在第一次使用类型的时候初始化static字段的值,在创建对象的时候会初始化实例字段的值。const字段和readonly,const=static readonly。
方法。静态方法和实例方法,方法重载。
嵌套类型。CLR将嵌套类型当做声明类型的静态成员,它不隶属于任何特定实例。嵌套类型的名字由外部类型名字限定,由于嵌套类型是外部类型的一部分,它能够访问外部类型的private字段。反之,外部类型并不能访问嵌套类型的private字段。
类型和初始化。类型初始化器,也就是static构造函数,每个类型只能有一个,没有参数,也没有返回值。静态字段声明在静态构造函数前面执行。
类型和接口。
类型和基类型。成员重载overloading和隐藏shadowing。
构造函数的执行顺序。C#和C++的处理策略不一样。
派生和构造,c#。
4. 用类型编程。在编程模型中,CLR将类型作为第一类概念。本章讨论了CLR程序中类型的显式用法,主要是元数据与运行时类型信息的角色。
a.运行时的类型。类型的实例分为值和对象,每个对象有一个“对象头”:一个同步块索引字段和一个指向对象类型的句柄。
对象头和类型句柄
每个类型都有一张接口表,包含了类型兼容的接口的入口项,对接口类型的转换通过这张表匹配;为了支持向直接或间接基类型的转换,类型结构还有一个指向基类型的指针。
类型的层次结构和运行时的类型信息
类型信息的数据结构可以通过Type访问,比如判断继承关系,可以用Type对象的IsSubClassOf或IsAssignableFrom,IsValueType判断是否值类型等等。
元数据编程。一般元数据编程是用来生成代码,这里只讨论读取元数据。
反射对象模型。其中Type.GetMembers可以获得类型中的全部的成员:字段、方法和嵌套类型
反射类型的层次结构
Type.GetMembers默认返回Type的公有约定中的部分成员,同时不包括静态成员,要想得到其他的成员,可以通过BindingFlags枚举指定。
using System;
using System.Reflection;
public sealed class Util
{
public static void DumpMembers(Type type)
{
// get the type‘s members
BindingFlags f = BindingFlags.Static
| BindingFlags.Instance
| BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.FlattenHierarchy;
MemberInfo[] members = type.GetMembers(f);
// walk the list of members
for (int i = 0; i < members.Length; i++)
Console.WriteLine("{0} {1} ",
members[i].MemberType,
members[i].Name);
}
}
使用反射的一个例子
5. 实例。CLR编程模型是基于类型、对象和值的。第四章讨论类型,本章则重点讨论对象和值。特别是概述了这两种实例模型的差别,包括值与对象在内存管理方面的不同。
6. 方法。所有组件之间的交互都是在方法调用中体现出来的。CLR提供了一系列技术,使得方法调用成为清晰的行为。本章将介绍这些技术,从实时编译时的方法初始化开始,直到类型异常时的方法终止。
方法和JIT编译。每个类型都有一张方法表,下面的例子是方法f调用情况。
class Bob {
static int x;
static void a() { x += 2; }
static void b() { x += 3; }
static void c() { x += 4; }
static void f()
{ c(); b(); a(); }
}
方法f对应的本机代码native code。
方法表的每个入口项指向一个唯一的存根例程(sub routine),初始化时,每个存根例程包含对CLR的JIT编译器的调用(PreStubWorker),在JIT编译器生成本机代码后,它会重写存根例程,插入一个jmp指令跳转到刚才生成的代码。
下图是的JIT编译和方法表,是JIT刚好编译f和c后的快照。
下图显示了JIT编译前后的方法存根
方法调用和类型。方法调用的CIL是call,虚方法调用对应的是callvirt。CIL的call指令静态地绑定本机IA-32指令到特定类型的方法表,callvirt产生一个特别的指令,它将根据目标对象的类型决定使用哪张表。每个类型的方法表中第一个区域是虚方法,包含基类的虚方法(最前面的是object的4个虚方法),第二个区域是非虚方法。
虚方法调用对应的native代码。第一句将this指针放到ecx中,第二句将类型句柄放到eax中,第三句根据方法表的偏移量调用方法。这个偏移量是类型加载时算出来的,不同的加载可能不一样,但是在一次加载中是不变的。
move ecx, esi
move eax, dword ptr [ecx]
call dword ptr [eax + 38h]
newslot和虚方法。每个虚方法在方法表中有一个插槽slot,如果虚方法声明为newslot,那么会分配一个新的插槽,这个插槽在所有基类型虚方法的后面;如果没有声明为newslot,那么会替换基类型中的相同签名的方法。
接口、虚方法和抽象方法。每个CLR类型都有一个方法表,CLR是这样安排这个方法表的:对于一个特定的声明接口,它的所有方法表插槽都是连续的。但是对于不同的类型,接口的方法表绝对位置可能不同,CLR使用接口偏移量表(interface offset table)来指名位置。
图6.4 使用基于接口引用的虚函数
C#支持两种实现接口方法的技术:一种是作为带有相同签名的公有方法,一种是实现相同签名的私有方法,方法名必须遵循InterfaceName.MethodNam的约定。例如对于接口IDrawable的Display方法,其实现的方法名为Idrawable.Display。前一种实现方法成为类公有签名的一部分,后者只能通过up-cast成接口才能访问。注意第二个Clone如果声明为public编译会报错(修饰符“public”对该项无效)。
CLR认为接口的方法是virtual的,也就是方法放在接口表的virtual部分,类继承接口如果没有显式声明virtual,CLR会当做virtual sealed处理(c#不能将同时使用virtual和sealed),也就是其派生类不能再override。下面是继承接口的例子。
判断下面例子中几个DoIt调用的方法。
第一个调用方法e,第二个调用e,第三个调用b,第四个调用c。想象下ReallyDerived方法表
显式方法调用。
7. 高级方法。CLR提供了强大的架构用来侦听方法调用。本章剖析了CLR侦听的实用部件,以及它对面向方面编程(aspect-oriented programming)的支持。
8. 域。CLR采用了AppDomain而不是OS进程,来划分代码的执行范围。本章从两个方面讨论AppDomain的角色,即作为底层OS进程的替代品,以及一个AppDomain与程序集解析器或加载器之间的交互。
9. 安全性。CLR一个主要的优点就是提供安全运行的环境,本章阐述了CLR加载器如何支持想代码授予特权,以及这些特权如何被强制实施的。
10. CLR外部环境。前9章都是讨论基于CLR编程模型的程序设计,最后一章则关注该编程模型对外的方式,以及它如何处理外部世界。