您的位置:首页 > 其它

《必须知道的.net》读书笔记(二)

2012-02-08 15:20 253 查看
第二部分本质——.Net深入浅出

第1章一切从IL开始

1引言

《The C programming Language》

2Hello World开始

3IL体验中心

应用 ILDasm.exe 反编译工具

编译后的 IL 结构中,包含了 MANIFEST 和 HelloWorld 类,其中 MANIFEST 是个附加信息列表,主要包含了程序集的一些属性。

3.1MANIFEST清单分析

.assembly 指令用于定义编译目标或者加载外部库。.assembly extern mscorlib 表示外部加载了外部核心库 mscorlib

我们知道 mscorlib.dll 程序集定义 managed code 依赖的核心数据类型,属于必须加载项。

在外部指令中还会指明了引用版本(.ver);应用程序实际公钥标记(.publickeytoken),公钥Token 是 SHA1 哈希码的低 8 位字节的反序

HelloWorld 程序集中包括了.hash algorithm 指令,表示实现安全性所使用的哈希算法,系统缺省为 0x00008004,表明为 SHA1 算法;.ver 则表示了 HelloWorld 程序集的版本号;

程序集由模块组成, .module 为程序集指令,表明定义的模块的元数据,以指定当前模块。

其他的指令还有:imagebase 为影像基地址;.file alignment 为文件对齐数值;.subsystem 为连接系统类型,0x0003 表示从控制台运行;.corflags 为设置运行库头文件标志,默认为1。

3.2HelloWorld类分析

.class public auto ansi beforefieldinit HelloWorld

extends [mscorlib]System.Object

{

} // end of class HelloWorld

 .class 表明了 HelloWorld 是一个 public 类,该类继承自外部程序集 mscorlib 的 System.Object 类。

 public 为访问控制权限,这点很容易理解。

 auto 表明程序加载时内存的布局是由 CLR 决定的,而不是程序本身

 ansi 属性则为了在没有被管理和被管理代码间实现无缝转换。没有被管理的代码,指的是没有运行在 CLR 运行库之上的代码。

 beforefieldinit 属性为 HelloWorld 提供了一个附加信息,用于标记运行库可以在任何时候执行类型构造函数方法,只要该方法在第一次访问其静态字段之前执行即可。如果没有 beforefieldinit 则运行库必须在某个精确时间执行类型构造函数方法,从而影响性能优化。

.method public hidebysig specialname rtspecialname

instance void .ctor() cil managed

{

// 代码大小 7 (0x7)

.maxstack 8

IL_0000: ldarg.0

IL_0001: call instance void [mscorlib]System.Object::.ctor()

IL_0006: ret

} // end of method HelloWorld::.ctor

 cil managed 说明方法体中为 IL 代码,指示编译器编译为托管代码。

 .maxstack 表明执行构造函数.ctor 期间的评估堆栈(Evaluation Stack)可容纳数据项的最大个数。关于评估堆栈,其用于保存方法所需变量的值,并在方法执行结束时清空,或者存储一个返回值。

IL_0000,是一个标记代码行开头,一般来说,IL_之前的部分为变量的声明和初始化。

 ldarg.0 表示装载第一个成员参数,在实例方法中指的是当前实例的引用,该引用将用于在基类构造函数中调用。

 call 指令一般用于调用静态方法,因为静态方法是在编译期指定的,而在此调用的是构造函数.ctor()也是在编译期指定的;而另一个指令 callvirt 则表示调用实例方法,它的调用过程有异于 call,函数的调用是在运行时确定的,首先会检查被调用函数是否为虚函数,如果不是就直接调用,如果是则向下检查子类是否有重写,如果有就调用重写实现,如果没有还调用原来的函数,依次类推直到找到最新的重写实现。

 ret 表示执行完毕,返回。

.method public hidebysig static void Main() cil managed

{

.entrypoint

// 代码大小 11 (0xb)

.maxstack 8

IL_0000: ldstr "Hello, world."

IL_0005: call void [mscorlib]System.Console::WriteLine(string)

IL_000a: ret

} // end of method HelloWorld::Main

.entrypoint 指令表明了 CLR 加载程序 HelloWorld.exe 时,是首先从.entrypoint 方法开始执行的,也就是表明 Main 方法将作为程序的入口函数。每个托管程序必须有并且只有一个入口点。这区别于将 Main 函数作为程序入口标志。

ldstr 指令表示将字符串压栈,"Hello, world."字符串将被移到 stack 顶部。CLR 通过从元数据表中获得文字常量来构造 string 对象。

hidebysig 属性用于表示如果当前类作为父类时,类中的方法不会被子类继承,因此 HelloWorld子类中不会看到 Main 方法。

3.3回归简洁

第2章认识IL代码——从基础到工具

1引言

《Applied Microsoft .NET Framework Programming》、《.NET 本质论》

2使用工具

ILadsm.exe 和 reflector.exe

3分析结构

4解析常用命令

下载[MSIL 指令速查手册]

4.1newobj和initobj

newobj 用于分配和初始化对象;而 initobj 用于初始化值类型。

newobj 在初始化过程中会调用构造函数;而 initobj 不会调用构造函数,而是直接对实例置空。

newobj 有内存分配的过程;而 initobj 则只完成数据初始化操作。

Initobj 还用于完成设定对指定存储单元的指针置空(null)。这一操作虽不常见,但是应该引起注意。

Newarr 指令用来创建一维从零起始的数组;而多维或非从零起始的一维数组,则仍由 newobj 指令创建。

String 类型的创建由 ldstr 指令来完成。

4.2call,callvirt,calli

 call 使用静态调度,也就是根据引用类型的静态类型来调度方法。

 callvirt 使用虚拟调度,也就是根据引用类型的动态类型来调度方法;

 calli 又称间接调用,是通过函数指针来执行方法调用;对应的直接调用当然就是前面的:call 和 callvirt。

第3章品味类型

3.1品味类型——从通用类型系统开始

1引言

CLI>CTS>CLS

2基本概念

通用类型系统定义了如何在运行库中声明、使用和管理类型,同时也是运行库支持跨语言集成的一个重要组成部分。

3位置和关系

CLI>CTS>CLS

4通用规则

类型转换

可以给类型创建别名

一个对象获得类型的办法是:obj.GetType()。

Typeof 操作符

可以使用 CLSCompliantAttribute将程序集、模块、类型和成员标记为符合 CLS 或不符合 CLS。

IL 中使用/checked+开关来进行基元类型的溢出检查,在 C#中实现这一功能的是 checked 和 unchecked 操作符。

3.2品味类型——值类型和引用类型

1内存有理

内存机制

值类型引用类型嵌套

2规则无边

string 类型是个特殊的引用类型,每次对 string 的改变都会在托管堆中产生一个新的 string 变量。

通常可以使用 Type.IsValueType 来判断一个变量的类型是否为值类型。

.NET 中以操作符 ref 和 out 来标识值类型按引用类型方式传递。

值类型与引用类型之间的转换过程称为装箱与拆箱。

sizeof()运算符用于获取值类型的大小,但是不适用于引用类型。

值类型使用 new 操作符完成初始化。

引用类型在性能上欠于值类型,,处理数据较小的情况时,应该优先考虑值类型。

值类型都继承自 System.ValueType,而 System.ValueType 又继承自 System.Object。

值类型不具有多态性;而引用类型有多态性。

值类型变量不可为 null 值,值类型都会自行初始化为 0 值;

值类型有两种表示:装箱与拆箱;引用类型只有装箱一种形式。

结构简单,不必多态的情况下,值类型是较好的选择;类型的性质不表现出行为时;数据较小的场合。

隐式显式转换

static 访问修饰符 转换修饰符 operator 类型(参数表)

3应用征途

执行分析,内存分析,IL分析

3.3参数之惑——传递的艺术

1引言

按值传递与按引用传递,ref和out,param修饰符

2参数基础论

行参和实参

3传递的基础

泛型类型参数和可变数目参数

param 关键字的实质是:param 是定制特性 ParamArrayAttribute 的缩写。

param 修饰的参数必须为一维数组。

param 必须在参数列表的最后一个,并且只能使用一次。

4传递的艺术

值类型实例传递的是该值类型实例的一个拷贝;简单的说对象作为参数传递时,执行的是对对象地址的拷贝,操作的是该拷贝地址。

按引用传递,传递的不是参数本身的值,而是参数的地址。如果参数为值类型,则传递的是该值类型的地址;如果参数为引用类型,则传递的是对象引用的地址。

不管是值类型还是引用类型,按引用传递必须以 ref 或者 out 关键字来修饰,方法定义和方法调用必须同时显示的使用 ref 或者 out。CLR允许通过 out 或者 ref 参数来重载方法。

不管参数本身是值类型还是引用类型,按引用传递时,传递的是参数的地址,也就是实例的指针。

第4章内存天下

4.1内存管理概要

4.1.1引言

l 对象创建时的内存分配。

l 垃圾回收。

l 非托管资源释放。

数据库链接、文件句柄、COM 对象等,仍然需要开发者自行清理。

4.1.2内存管理概要

4.2对象创建始末

4.2.1引言

内存分配和初始化

4.2.2内存分配

l newobj,用于创建引用类型对象。

l ldstr,用于创建 string 类型对象。

l newarr,用于分配新的数组对象。

l box,在值类型转换为引用类型对象时,将值类型字段拷贝到托管堆上发生的内存分配。

4.3垃圾回收

— .NET 垃圾回收机制

— 非托管资源的清理

那些不可达对象则被认为是可回收对象

垃圾收集器周期性的执行内存清理工作,一般在以下情况出现时垃圾收集器将会启动:

(1)内存不足溢出时,更确切地应该说是第 0 代对象充满时。

(2)调用 GC.Collect 方法强制执行垃圾回收。

(3)Windows 报告内存不足时,CLR 将强制执行垃圾回收。

(4)CLR 卸载 AppDomain 时,GC 将对所有代龄的对象执行垃圾回收。

(5)其他情况,例如物理内存不足,超出短期存活代的内存段门限,运行主机拒绝分配内存等等。

内存释放之前 GC 会首先检查终止化链表中是否有记录来决定在释放内存之前执行非托管资源的清理工作,然后才执行内存释放。

GC 在垃圾回收之后,堆上将出现多个被收集对象的“空洞”,为避免托管堆的内存碎片,会重新分配内存,压缩托管堆。CLR 垃圾收集器使用了 Generation 的概念来提升性能,还有其他一些优化策略,如并发收集、大对象策略等,来减少垃圾收集对性能的影响。

CLR 提供了两种收集器:工作站垃圾收集器(Workstation GC,包含在 mscorwks.dll)和服务器垃圾收集器(Server GC,包含在 mscorsvr.dll)。

在 CLR 加载到进程时,可以通过 CorBindToRuntimeEx()函数来选择执行哪种收集器,选择合适的收集器也是有效、高效管理的关键。

垃圾收集器将托管堆中的对象分为三代,分别为:0、1 和 2。

垃圾收集器总是首先收集第 0 代的不可达对象内存。代龄机制在垃圾回收中的性能优化作用。

仅当第 0 代对象释放的内存不足以创建新的对象,同时 1 代对象的体积也超出了容量阙值时,垃圾收集器将同时对0 代和 1 代对象进行垃圾回收。

1非托管资源清理

主要有两种方式:Finalize 方法和 Dispose 方法

在继承链中所有实例将递归调用 base.Finalize 方法,也就是意味调用终结器释放资源时,将释放所有的资源,包括父类对象引用的资源。

对于重写了 Finalize 方法的类型来说,可以通过 GC. SuppressFinalize 来免除终结。

l 终止化操作的时间无法控制,执行顺序也不能保证。因此,在资源清理上不够灵活,也可能由于执行顺序的不确定而访问已经执行了清理的对象。

l Finalize 方法会极大地损伤性能,GC 使用一个终止化队列的内部结构来跟踪具有 Finalize 方法的对象。

l 重写了 Finalize 方法的类型对象,其引用类型对象的代龄将被提升,从而带来内存压力。

l Finalize 方法在某些情况下可能不被执行,例如可能某个终结器被无限期的阻止,则其他终结器得不到调用。

对于 Finalize 方法,有以下规则值得总结:

l 在 C#中无法显示的重写 Finalize 方法,只能通过析构函数语法形式来实现。

l struct 中不允许定义析构函数,只有 class 中才可以,并且只能有一个。

l Finalize 方法不能被继承或重载。

l 析构函数不能加任何修饰符,不能带参数,也不能被显示调用。

l 执行垃圾回收之前系统会自动执行终止化操作。

l Finalize 方法中,可以实现使得被清理对象复活的机制,不过这种操作相当危险。

实现 Dispose 模式的典型操作中,有几点说明:

l Dispose 方法中,应该使用 GC. SuppressFinalize 防止 GC 调用 Finalize 方法,因为显式调用 Dispose 显然是较佳选择。

l 公有 Dispose 方法不能实现为虚方法,以禁止在派生类中重写。

l 在该模式中,公有 Dispose 方法通过调用重载虚方法 Dispose(bool disposing)方法来实现,具体的资源清理操作实现于虚方法中。两种策略的区别是:disposing 参数为真时,Dispose 方法由用户代码调用,可释放托管或者非托管资源;disposing 参数为假时,Dispose 方法由 Finalize 调用,并且只能释放非托管资源。

l disposed 字段,保证了两次调用 Dispose 方法不会抛出异常,值得推荐。

l 派生类中实现 Dispose 模式,应该重写基类的受保护 Dispose 方法,并且通过 base 调用基类的Dispose 方法,以确保释放继承链上所有对象的引用资源,在整个继承层次中传播 Dispose 模式。

最佳的资源清理策略,应该是同时实现 Finalize 方式和 Dispose 方式。任何重写了 Finalize 方法的类型都应实现 Dispose 方法。

凡是实现了 Dispose 模式的类型,均可以 using 语句来定义其引用范围。

4.4性能优化的多方探讨

— .NET 性能优化的策略探讨

— 多种性能优化分析

4.4.1引言

什么才算良好的软件产品?业务流程、用户体验、安全性还有性能,一个都不能少。

4.4.2性能条款

¡ Item1:推荐以 Dispose 模式来代替 Finalize 方式。

¡ Item2:选择合适的垃圾收集器:工作站 GC 和服务器 GC。

¡ Item3:在适当的情况下对对象实现弱引用。

弱引用是对象引用的一种“中间态”,实现了对象既可以通过 GC 回收其内存,又可被应用程序访问的机制。对胖对象的内存性能带来提升。

WeakReference 类用于表示弱引用,通过其 Target 属性来表示要追踪的对象,通过其值赋给变量来创建目标对象的强引用。

MyClass mc = new MyClass();

//创建弱引用

WeakReference wr = new WeakReference(mc);

//移除强引用

mc = null;

if (wr.IsAlive)

{

//弱引用转换为强引用,对象可以再次使用

mc = wr.Target as MyClass;

}

else

{

//对象已经被回收,重新创建

mc = new MyClass();

}

¡ Item4:尽可能以 using 来执行资源清理。

¡ Item5:推荐使用泛型集合来代替非泛型集合。

泛型集合并不能完全代替非泛型集合的应用。

¡ Item6:初始化时最好为集合对象指定大小。

¡ Item7:特定类型的 Array 性能优于 ArrayList。

¡ Item8:字符串驻留机制,是 CLR 为 String 类型实现的特殊设计。

¡ Item9:合理使用 System.String 和 System.Text.StringBuilder。

在简单的字符串操作中使用 String,在复杂的字符串操作中使用 StringBuilder。StringBuilder 在使用上,最好指定合适的容量值。

¡ Item10:尽量在子类中重写 ToString 方法。

能在一定程度上减少装箱操作的发生。

¡ Item11:其他推荐的字符串操作。

int StringCompare(string str1, string str2)

以 Length 属性来判断字符串判空

¡ Item12:for 和 foreach 的选择。

推荐选择 foreach 来处理可枚举集合的循环结构。

¡ Item13:以多线程处理应对系统设计。

推荐在多线程编程中使用线程池,.NET 提供了 System.Threading.ThreadPool 类来提供对线程池的封装,一个进程对应一个 ThreadPool,可以被多个 AppDomain 共享。

ThreadPool.QueueUserWorkItem(new WaitCallback(th.MyProcOne), "线程 1");

¡ Item14:尽可能少地抛出异常,禁止将异常处理放在循环内。

尽量用逻辑流程控制来代替异常处理。

¡ Item15:捕获异常时,catch 块中尽量指定具体的异常筛选器,多个 catch 块应该保证异常由特殊到一般的排列顺序。

¡ Item16:struct 和 class 的性能比较。

¡ Item17:以 is/as 模式进行类型兼容性检查。

以 is 来实现类型判断,以 as 实现安全的类型转换。

¡ Item18:const 和 static readonly 的权衡。

const 高效,readonly 灵活。

¡ Item19:尽量避免不当的装箱和拆箱,选择合适的代替方案。

¡ Item20:尽量使用一维零基数组。

¡ Item21:以 FxCop 工具,检查你的代码。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: