收藏 分享(赏)

CLR笔记:2 范本.doc

上传人:杨桃文库 文档编号:9090642 上传时间:2019-07-23 格式:DOC 页数:67 大小:1.75MB
下载 相关 举报
CLR笔记:2 范本.doc_第1页
第1页 / 共67页
CLR笔记:2 范本.doc_第2页
第2页 / 共67页
CLR笔记:2 范本.doc_第3页
第3页 / 共67页
CLR笔记:2 范本.doc_第4页
第4页 / 共67页
CLR笔记:2 范本.doc_第5页
第5页 / 共67页
点击查看更多>>
资源描述

1、CLR 笔记目录 CLR 笔 记:1.CLR 的执行模型CLR 笔 记:2.生成,打包,部署,管理 CLR 笔 记:3.共享程序集合强命名程序集 CLR 笔 记:4.类型基础 CLR 笔 记:5.基元,引用和值类型 CLR 笔 记:6.类型和成员基础CLR 笔 记:7.常量和字段 CLR 笔 记:8.方法 CLR 笔 记:9.PropertyCLR 笔 记:10.事件CLR 笔 记:11.字符串CLR 笔 记:12.枚举类型和位标志 CLR 笔 记:13.数组 CLR 笔 记:14.接口CLR 笔 记:15.委托 CLR 笔 记:16.泛型 CLR 笔 记:17.自定义属性CLR 笔 记:18

2、.可空值类型CLR 笔记:1.CLR 的执 行模型 术语: CLR :Common Language Runtime 公共语言运行期,有多种不同编程语言使用的运行库托管模块:Managed Module,一个 标准的 MS Window 可移植执行体文件(32 位 PE32 或 64 位 PE32+)IL:Intermediate Language 中间语言,又叫托管代码(由 CLR 管理它的执行)元数据:metadata,一系列特殊的数据表程序集:Assembly,抽象的JIT:just-in-time 即时编译,将 IL 编译成本地 CPU 指令(本地代码)FCL:Framework Cl

3、ass Library,Framework 类库CTS:Common Type System,通用类型系统,描述了类型的定义及其行为方式CLI:Common Language Infrastructure,公共语言基础结构,这是 MS 提交给 ECMA 的一个标准,由 CTS和其他 Framwork 组件构成CLS:Common Language Specfication,公共 语言规范,详细规定了一个最小特性集1.1 将源代码编译成托管模 块CLR 编译过 程: C#源码文件C#编译器编译托管模块(IL 和元数据)托管模块的各个部分:1.PE32 或 PE32+头 标志了文件类型,GUI/C

4、UI/DLL,文件生成时间,在 32 位还是 64 位上运行2.CLR 头CLR 版本,入口方法,模 块元数据, 资源,强名称3.元数据3 种类 型的表4.IL 代码元数据包括:1.描述了模块中定义的内容,比如类及其成员2.指出了托管模块引用的内容,比如导入的类及其成员3.清单 manifest,描述了构成 Assembly 的文件,由 Assembly 中的文件实现的公共导出类型,与 Assembly 相关联的资源/数据文件元数据总是嵌入到与代码相同的 EXE/DLL 中,始 终与 IL 保持同步。元数据用途:1.消除了对头/库文件的依赖,直接从托管模块中读取2.智能感知,从元数据中解析3.

5、代码验证,使用元数据确保代码只执行安全操作4.正反序列化5.垃圾收集器跟踪对象的生存期以及对象的类型1.2 将托管模块合并成程序集程序集:一个或多个托管模块/资源文件的逻辑分组,是最小的重用,安全性以及版本控制单元。既可以生成但文件程序集,也可以生成多文件程序集, 这由编译器工具决定。CLR 是和程序集一起工作的,而不是和托管模 块1.3 加载 CLRCLRVer 命令,查看机器上所有 CLR 版本csc 的 /plattform 开关,决定生成什么样的程序集:AnyCPU,x86 ,x64,Itanium1.4 执行 Assembly 代码ILAsm 命令,将 IL 编译成 Assembly

6、;ILDasm 将 Assembly 编译成 IL。高级语言(C#)只是 CLR 的一个子集, IL 则允许访问 CLR 的所有功能。JITCompiler 函数,又名 JIT 编译器(JITter)在方法首次执行时,CLR 检测出 Main 的代码引用的所有类型,于是 CLR 分配一个内部数据结构,用于管理 对引用类型的访问。在这个内部结构中,每个方法都有一条对应的纪录以及地址。对此结构进行初始化时,CLR 将每条纪录都设置为 CLR 内部包含的一个未文档化的函数,即 JITCompiler 函数。JITCompiler 函数被调用时,找到相 应方法的 IL,编译成本地 CPU 指令,并保存

7、到一个动态内存块中,将该内存地址存入内部结构中,最后 JITCompiler 函数会跳转到内存块中的代码,执行。第二次执行该方法时,不需要再编译,直接执行内存块中的代码。JIT 将本地代码保存在 动态内存中,一旦程序终止,本地代码会被丢弃。csc 命令有 2 个开关会影响代码的优化:/optimize ,/debug开关设置 IL 代码质量 JIT 本地代码质量/optimize- ,/debug- 未优化 优化 默认设置/optimize- ,/debug(+/full/pdbonly) 未优化 未优化 VS2005 Degug 状态/optimize+ ,/debug(-/full/pdb

8、only) 优化 优化 VS2005 Release 状态生成未优化的 IL 时,会在 IL 中生成 NOP 指令用于调试,设置断点。IL 是基于堆栈的。所有指令都是:将操作数压栈,结果则从 栈中弹出IL 有安全验证机制,保证每一行 IL 代码是正确的,不会非法访问内存,每个托管 EXE 都在独自的 AppDomain 中运行。不安全代码:允许 C#直接操作内存字 节,在 COM 互操作 时使用,csc 以/unsafe 开关标记包含不安全代码,其中所有方法都使用 unsafe 关键字。PEVerify 命令 检查程序集所有方法,指出其中的不安全代码方法。1.5 本地代码生成器 NGEN.ex

9、eNGEN.exe 将 IL 预先编译到硬盘文件中,可以加快程序的启动速度,减小程序的工作集 (所有加载该程序集的 AppDomain不再 copy 其副本,因为该程序集已 经与编译到文件中,是代码共享的) 。缺点是:不能保护 IL 外泄生成的文件可能失去同步因为在文件中要计算首选基地址,而 NGEN 是静态计算好的,所以要修改基地址,速度会慢下来较差的执行性能,NGEN 生成的代码没有 JIT 好。如果不能使用 NGEN 生成的文件,会自动加载 JITCompiler。1.7 CTSCTS 的一些规定:1.一个类 型可以包含 0 个或多个成员2.类型可 视化以及类型成 员的访问规则3.定义了

10、 继承,虚方法,对象生成期的管理规则4.所有类 型最终都从预 定义的 System.Object 继承1.8 CLS如果在 C#中定义的类型及其方法,可以在 VB 中使用,那么,就不能在 C#中定义 CLS 外的任何 public/protected 特性,privated 的类型及其成员不受限制。C#可以有仅大小写不同的两个方法不符合 CLS,所以不能是 public 的。使用assembly:CLSComplant(true) 标志程序集,告诉编译器检查该程序集的 CLS 相容性。书上写得不明白,我这里做了一个测试:using System;assembly: CLSCompliant(t

11、rue)namespace ClassLibrary2public class Class1public void A()public void a()注意,assembly:CLSComplant(true)要写在 namespace 外。我定义了两个不同方法 A 和 a,编译器会有警告,说这样的语法不兼容 CLS;如果去掉assembly:CLSComplant(true)声明,那么不会有这个警告;如果将 a 方法改为 private,则不会有警告。中途我使用了 ILDasm 观察这个 dll,发现两个方法 A 和 a 都存在于 IL 中,说明 IL 的语法范围也大于 CLS。在 VB 中

12、,我添加了对此 dll 的引用:Imports ClassLibrary2Module Module1Public Class TPublic Function A() As IntegerDim c1 As Class1 = New Class1()End FunctionEnd ClassEnd Module发现,在 c1.后面不会有 A 或 a 方法的智能感知, 说明 VB 不能识别不符合 CLS 的语法。如果修改了 dll 中的 a 方法为 private 或者删除 a 方法,则在 VB 中可以智能感知到 A 方法。可以得出结论,不符合 CLS 的语法,在另一 种语言中是看不到的。1.

13、9 COM 互操作3 种互操作情形:1.托管代 码可以调用 DLL 中包含的非托管函数,如 Kernal32.dll,User32.dll2.托管代 码可以使用现 成的 COM 组件3.非托管代 码可以使用托管 类型(C#写的 ActiveX 控件或 shell 扩展)CLR 笔记:2.生成,打包, 部署 ,管理 2.1 .NET Framework 部署目标非.NET 程序的 问题:1.DLL hell2.安装复杂 。目录分散,注册表,快捷方式3.安全性。悄悄下载恶意代码2.2 将类型集成到模块中 编译器工具 csccsc /out:Program.exe /t:exe /r:Mscorli

14、b.dll Program.cs由于 C#会自动 引用 Mscorlib.dll,可以省略 /r:Mscorlib.dll C#默认生成 exe(CUI), 所以/t:exe 可以省略;dll(程序集 /t:library)和 GUI(可视化应用程序 /t:winexe)时不可以省略C#默认编译成 Program.exe,所以/out:Program.exe 可以省略 最后精简为:csc Program.cs如果不希望默认引用 Mscorlib.dll,使用 /nostdlib 开关csc /nostdlib Program.cs注:/t 可以写为/target,/r 可以写为/referen

15、ce/reference:指定引用的 dll,可以使用完整路径;如果是不完整的,在以下目录依次查找:1.工作目 录(要编译的 cs 文件所在)2.系 统目录(csc.exe 所在)3./lib 开关指定的目录4.LIB 系统变量指定的目 录应答文件(Response File)包括一系列编译器命令行开关,执行 csc 时会将其打开,例如 MyProject.rsp 中有以下文本:/out:Program.exe /t:exe /r:Mscorlib.dll那么调用如下:csc MyProject.rsp Program.cs这个应答文件的位置,运行 csc 命令时,先在当前目录(Program

16、.cs 所在)查找;后在系统目录(csc.exe 所在)查找,如果都有就以前者为准使用/noconfig 开关指定忽略 rsp 文件2.3 元数据概述 3 种类别的表:定 义表,引用表,清单表1.常见的定 义表:ModuleDef, TypeDef,MethodDef,FieldDef,ParamDef,PropertyDef,EventDef2.常见的引用表: AssemblyRef,ModuleRef,TypeRef,MemberRef3.常见的清 单表:AssemblyDef, FileDef,ManifestResourceDef,ExportedTypesDef2.4 合并模块以构成

17、一个程序集CLR 总是首先加载包含清单表的文件,然后使用 这个清单,加 载其他文件。使用多文件程序集的 3 个理由:1.按类别 划分类型,放到不同的程序集中2.可以添加 资源/数据文件,使用 AL.exe,使其成 为 程序集的一部分3.程序集的各个 类型可以使用不同的 语言来实现,然后使用 ILAsm 生成 ILcsc /t:module A.cs 指示编译器生成不含清 单表的清单文件,一般 总是一个 DLL,生成的文件为 A.netmodule接下来,要把这个 netmodule 文件附加到一个有清单表的程序集中,使用 addmodule 开关:csc /out:FinalAssmbly.d

18、ll /t:library /addmodule:A.netmodule B.cs 这里 B.cs 包含清 单表,最终生成 FinalAssmbly.dll,如果 A.netmodule 不存在,便一起会报错。但是运行程序时,A.netmodule 可以不存在,仅在调用其中的方法时,才会加载 A.netmoduleVS2005 不支持创建多文件程序集。VS2005 中添加引用的 “.NET 选项”, 对应注册表中 HKEY_LOCAL_MACHINESOFTAREMicrosoft.NETFrameworkAssemblyFolders,动态添加键值,VS2005 可以在 对应的目录下找到 d

19、ll,并加载到“.NET 选项”中。IL 中 Token:0x26000001,000001 代表行号,0x26 代表 FileRef,此外 0x01=TypeRef,0x02=TypeDef,0x03=AssemblyRef,0x27=ExportedType。AL.exe 程序集链接器生成一个 DLL,只包括一个清单文件,不包含 IL 代码,以下生成的是 FinalAssmbly.dll:AL /out:FinalAssmbly.dll /t:library /addmodule:A.netmodule B.netmodule还可以生成 CUI 或 GUI,但很少 这么做,因 为要添加 /

20、main 开关,指定入口方法:AL /out:FinalAssmbly.dll /t:exe /main:Program.Main /addmodule:A.netmodule B.netmodule在程序集中包含资 源文件, 书上讲到了 3 个开关:/embledresource 嵌入到程序集中,更新清单表的 ManifestResourceDef对应 csc 的/resource 开关/linkresource 并不嵌入到程序集中,更新清单表的 ManifestResourceDef 和 FileDef,对应 csc 的/linkresource 开关/win32res 嵌入标准的 Win

21、32 文件/win32icon 嵌入 ico 文件2.5 程序集版本资源信息使用 System.Diagnostics.FileVersionInfo 的静态方法 GetVersionInfo 获取这些信息。在 VS2005 中, 这些信息存放在 AsseblyInfo.cs 中。使用 AL 生成程序集 时,可以指定开关,来配置这些信息,表从略(见书)2.6 语言文化附属程序集 satellite assembly,使用一种具体的语言文化来标记的程序集。使用 AL 时 ,通过/culture: text 来指定语言文化,这里 text 为 en-US,zh-CN 等等。也可以直接写在程序集中,

22、使用自定义属性:assembly:AssemblyCulture(“en-US“)使用 System.Resource.ResourceManager 来访问附属程序集的资源。2.7 简单应用程序部署这一节讲的是私有部署方式(private deployed assembly),即部署到和应用程序相同的目录中的程序集2.8 简单管理控制CLR 定位程序集 A 时,对于中性 neatual 语言文化,按照配置文件 privatePath 属性顺序,先后扫描 privatePath 指定的目录,直到找到所需:先找 A.dll,如下:AppDirAsmName.dllAppDirAsmNameAsm

23、Name.dllAppDirfirstPrivatePathAsmName.dllAppDirfirstPrivatePathAsmNameAsmName.dllAppDirsecondPrivatePathAsmName.dllAppDirsecondPrivatePathAsmNameAsmName.dll如果没有,重头再来找 A.exe附属程序集遵循同样规则,只是目录变为 privatePath+“文化名称(如 en-US,先找 dll,再找 exe;如果没有找到,就把文化名称改为 en,重头再来 )“CLR 笔记:3.共享程序集合强命名程序集 3.1 两种程序集,两种部署CLR 有两种

24、程序集,弱命名程序集和 强命名程序集,二者基本一样,区别:强命名程序集时用发布者的公钥/私钥对 进行了签名,唯一性的标识了程序集的 发布者。弱命名程序集只能私有部署, 强命名程序集可以使用全局部署,也可以私有部署。3.2 为程序集指派强名称一个强命名的程序集包括 4 部分重要属性, 标志唯一:一个无扩展名的程序集,一个版本号,一个语言文化标志,一个公钥publickey。此外, 还使用发布者的私 钥进行签名MyTypes,Version=1.0.8123.0,Culture=neatral,PublicKeyToken=xxxxxxxxxxxxxxxx(公钥标记)MS 使用公钥/私钥加密技 术

25、, 这样,没有两家公司有相同的公钥/私钥对( 除非他们共享公钥/私钥对)。使用反射获取强命名程序集的 PublicKeyToken创建强命名程序集的步骤:1.生成公 钥/私钥对:使用 SN 命令, 这个命令所有开关 都区分大小写SN -k MyCompany.keys这里 MyCompany.keys 是创建的文件名2.将原有程序集升 级为强 命名程序集csc /keyfile:MyCompany.keys app.cs这里,app.cs 是包含清单表的文件,不能对不包含清单表的文件签名。C#编译器会打开 MyCompany,使用私钥对程序集进行签名,并在清 单中嵌入公钥。 用私钥签名一个文件

26、:是指生成一个强命名程序集时,程序集的 FileDef 清单中列出了包含的所有本件,将 每个文件名称添加到清单中,文件的内容都会根据私 钥进行哈希处理,得到的哈希值与文件名一起存入 FileDef 中。这个哈希值称为 RSA 数字签名。最终,生成的包含清单的 PE32 文件,其中会含有 RSA 数字签名和公钥补充 1:签名默认使用 SHA-1 算法,也可以使用别的算法,通过 AL 命令的/algid 开关指定。补充 2,还可以使用 SN 命令,在原有基础上,得到只含公钥的文件并显示:SN -p MyCompany.keys MyCompany.PublicKey这里 MyCompany.Pub

27、licKey 是创建的公钥文件名SN -pt MyCompany.PublicKey显示公钥与公 钥标记 补充 3:在 IL 中,Local 对应于 Culture补充 4:公钥标记是公钥的最后 8 个字节。AssemblyRef 中存的是公 钥标记, AssemblyDef 存的是公钥。3.3 GAC 全局程序集缓存GAC 一般在 C:WindowsAssembly,结构化的,有很多子目录。使用 Windows Explorer shell 扩展来浏览 GAC 目录,这个工具是在安装 Framework 时附带的。不能使用手动方法复制程序集文件到 GAC,要使用 GACUtil 命令。只能安

28、装强命名程序集到 GAC 中,而且要有 Admin/PowerUser 权限。GAC 的好处是可以容纳一个程序集的多个版本。每个版本都有自己的目录。缺点是违反了简单安装的原则。3.4 在生成的程序集中引用一个强命名程序集 第 2 章有讲到, 对于不完整路径,csc 编译时目录查找顺序:1.工作目 录(要编译的 cs 文件所在)2.系 统目录(csc.exe 所在,同时也包括 CLR DLL)3./lib 开关指定的目录4.LIB 系统变量指定的目 录安装 Framework 时,会安装.NET 程序集两套副本,一套在编译器/CLR 目录便于生成程序集,另一套在 GAC 子目录便于在运行时加载它

29、们。编译时并不去 GAC 中查找。3.5 强命名程序集能防范篡 改在安装强命名程序集到 GAC 时,系统对包含清单的文件内容进行哈希处理,并将这个值与 PE32 文件中嵌入的 RSA 数字签名进行比较,如果一致,就再去比较其他文件内容( 也是哈希处理在比 RSA 签名)。一旦有一个不一致,就不能安装到 GAC。如果强命名程序集安装在 GAC 以外的目录, 则会在加载时比较签名。3.6 延迟签名( 部分签名) delayed signing开发阶段会使用到这个功能允许开发人员只用公钥来生成一个程序集,而不需要私钥 。编译时,会预留一定空间来存 储 RSA 数字签名,不 对文件内容进行哈希处理。

30、CLR 会跳过对哈希值的检查。以后可以再对其进行签名。步骤如下:1.生成程序集:csc /keyfile: MyCompany.PublicKey /delaysign: MyAssembly.cs2.跳 过对 哈希值 的检查: SN.exe -Vr MyAssembly.dll3.准 备私 钥,再次进行签名: SN.exe -R MyAssembly.dll MyCompany.PrivateKey4.再次延迟签名: SN.exe -Vu MyAssembly.dll3.7 私有部署强命名程序集强命名程序集如果不在 GAC 中,每次加载都要进行验证,有性能损失。还可以设计为局部共享强命名程序

31、集,指定配置文件的 codeBase 即可。3.8 运行库如何解析类型引用在 TypeRef 中查找类型引用的纪录, 发现其强签名,然后定位这个程序集的所在位置:会在以下三个地方查找:1.同一个文件: 编译时 就能发现(早期绑定)2.不同的文件,但同一个程序集:在 FileRef 表中3.不同的文件,不同的程序集:这时要加载被引用的程序集,从中查找注:AssemblyRef 使用不带扩 展名的文件名来引用程序集。绑定程序集时,系统通过探测 xx.dll 和 xx.exe 来定位文件。ModuleDef,ModuleRef,FileDef 使用文件名及其扩展名来引用文件。注:在 GAC 中查找程

32、序集时,除了名称,版本,语言文化和公钥,还需要 CPU 体系结构,而且是先根据 这个体系结构查找。CLR 笔记:4.类型基础 4.1 所有类型都派生自 System.ObjectSystem.Object 提供的方法: GetType(),ToString(),GetHashCode(),Equals(),MemberwiseClone(),Finalize()所有对象都是用 new 操作符创建,创建过程:1. 计算 对象大小,包括“类型对象指针”和“同步块索引 ”2.从托管堆分配 对象的内存3.初始化 对象的“类型 对象指针”和“同步块索引”4.调用 ctor,传入相应 参数最终会调用到 S

33、ystem.Object 的 ctor,该 ctor 是空操作5.返回新 对象的引用/指针4.2 强制类型转换类型安全,CLR 的最重要特性之一。1.对象 转成其基类,不需要任何特殊语法,默认为安全隐式转换 Object o = new Employee(); 将 new Employee 转为 Object 基类,可以看作:Employee e = new Employee(); Object o = e;2.对象 转成其子类,要显示转换 Employee e = (Employee)o;但是,即使显示转换,也会在运行期错误基于以上原则,有 类型安全性检测:http:/ 和 as 操作符is

34、:检查 一个对象是否兼容于指定的类型,并返回一个 bool 值即使类型不对,仅返回 false,不会抛出异常;null 对象则返回 falseif (o is Employee)Employee e = (Employee)o;上述代码检测两次 对象类型,一次在 if 中的 is,另一次在显示转型时会影响性能,使用 as 代替。as:用来简化上述代码:永远不会抛出异常,如果 对象不能转型,就返回 null:Employee e = o as Employee;if (e != null) /执行操作4.3 命名空间和程序集CLR 不知道 namespace 概念,using 是 C#的语法,C

35、LR 只认识类型的全称C#会自动在 MSCorLib.dll 中查找所有核心 FCL 类型,如 Object,Int32,String记住以下语法:using System = NameSpaceAnotherName;4.4 运行时相互关系CLR 笔记:5.基元,引用和值类型 5.1 基元类型编译器(C#)直接支持的任何数据 类型都称为基元类型(primitive type),基元类型直接映射到 FCL 中存在的类型。可以认为 using string = System.String;自动产生。FCL 中的类型在 C#中都有相 应的基元类型,但是在 CLS 中不一定有,如 Sbyte,UIn

36、t16 等等。C#允许在“安全”的时候隐式转型不会发生数据丢失,Int32 可以转为 Int64,但是反过来要显示转换,显示转换时 C#对结果进行截断处理。unchecked 和 check 控制基元类型操作C#每个运算符都有 2 套 IL 指令,如+对应 Add 和 Add.ovf,前者不 执行溢出检查,后者要检查并抛出 System.OverflowException 异常。溢出检查默认是关闭的,即自 动对应 Add 这样的指令而不是 Add.ovf。控制 C#溢出的方法:1.使用 /check+编译器开关2.使用操作符 checked 和 unchecked:int b = 32767;

37、 / Max short value/b = checked(short)(b + 32767); throw System.OverflowExceptionb = (short)checked(b + 32767); /return -2这里,被注释掉的语句肯定会 检查到溢出,运行期抱 错;而第二句是在 Int32 中检查,所以不会溢出。注意这两条语句只是为了说明 check 什么时候发挥作用,是两条不同 语义的语句,而不是一条语句的正误两种写法。3.使用 checked 和 unchecked 语句,达到与 check 操作符相同的效果:int b = 32767; / Max shor

38、t valuecheckedb = b + 32767;return (short)b;System.Decimal 类型在 C#中是基元,但在 CLR 中不是,所以 check 对其无效。5.2 引用类型和值类型引用类型从托管堆上分配内存, 值类型从一个线程堆栈分配。值类型不需要指针,值类型实 例不受垃圾收集器的制约struct 和 enum 是值类型,其余都是引用类型。这里,Int32,Boolean,Decimal,TimeSpan 都是结构。struct 都派生自 System.ValueType,后者是从 System.Object 派生的。 enum 都派生自 System.Enu

39、m,后者是从 System.ValueType 派生的。值类型都是 sealed 的,可以实现接口。new 操作符对值类 型的影响:C#确保值类型的所有字段都被初始化 为 0,如果使用 new,则 C#会认为实例已经被初始化;反之也成立。SomeVal v1 = new SomeVal();Int32 a1 = v1.x; /已经初始化为 0SomeVal v2;Int32 a2 = v2.x; /编译器报错,未初始化使用值类型而不是引用类型的情况:1.类 型具有一个基元类型的行为:不可变类型,其成 员字段不会改 变2.类 型不需要从任何类型继承3.类 型是 sealed 的4.类 型大小:或

40、者类型实例较小(接口的 Equals 方法。运算符重载=和!=如果还需要排序功能,那额外做的事情就多了:要实现 System.IComparable 的 CompareTo 方法和 System.IComparable的 CompareTo 方法,以及重 载所有比较运算符 ,=5.6 对象哈希码重写 Equals 方法的同时,要重写 GetHashCode 方法,否则编译器会有警告。因为 System.Collection.HashTable 和 Generic.Directory 的实现中,要求 Equal 的两个对象要具有相同的哈希码。HashTable/Directory 原理:添加一个

41、 key/value 时,先获取该键值对的 HashCode;查找时,也是 查找这个HashCode 然后定位。于是一旦修改 key/value,就再也找不到这个键值对,所以修改的做法是,先移除原 键值对 ,在添加新的键值对。不要使用 Object.GetHashCode 方法来获取某个对象的唯一性。FCL 提供了特殊的方法来做这件事:using System.Runtime.CompilerServices;RuntimeHelpers.GetHashCode(Object o)这个 GetHashCode 方法是静态的,并不是对 System.Object 的 GetHashCode 方法

42、重写。System.ValueType 实现的 GetHashCode 方法使用的是反射技术。CLR 笔记:6.类型和成员基础 1.Class 的可见性有 public 和 internal 两种,public 对所有程序集都可见,internal 仅对其所在的程序集可 见。默认是 public 的。2.友元程序集,使用 friend assembly 可以实现单元测试,而不使用反射技术。书上讲的是按照命令行编译。我测试用的是 vs2005 的 solution,如下:3.成员的可访问性成员默认是 private 的,接口类型的成员都是 public 的。子类重写父类的成员时,原始成 员与重写

43、成员要有相同的可 访问性 C#的约束;CLR 的约束是,重写成 员的可访问性不能更低。CLR 和 C#是不一样的,如表:CLR 术语 C#术语Private privateFamily protectedFamily and Assembly 不支持Assembly internalFamily or Assembly protected internalPublic public4.静态类static 只能用于 class,不能用于 struct,因为 CLR 要求值必须实例化,而且不能控制实例化过程。C#对静态类的约束:静态类必须直接从 System.Object 派生静态类不能实现任何接

44、口静态类只能定义静态成员:字段,方法,属性,事件静态类不能用作:字段,方法,参数,局部变量。在 MSIL 中,不会为静态类生成 ctor,会将其标记为 abstract 和 sealed5.部分类CLR 不支持 partial,只是 C#的语法。所以某个类型的源码必须使用同一种编程语言6.组件,多态 和版本控制.NET 版本号 2.7.1.34,包含 4 个部分:主版本号,次版本号,内部版本号,修订版本号。修订版本,向后兼容,改变内部/修订版本号;发布新版本,不向后兼容,改变主/次版本号。多态中,子类重写父类的虚方法,会引起版本控制问题,即父类发生改变,其版本低于子类版本,会导致子类行为变化。

45、C# 5 个用于 类/类成员 的 影响组件版本控制 的 关键字:abstract:用于类/类成员virtual 和 override:用于成员sealed:用于类/类成员。用于成员时,仅用于重写了虚方法的方法。new,用于类/类成员/常量/字段C#调用虚方法:CLR 允许类中定义多个“同名方法“,仅仅是返回类型不同,IL 允许这样做;C#不允许,忽略返回 值的类型,相应的用“转换操作符“实现 IL 中的“同名方法“。调用方法相应的 MSIL:一个是 call,用来调用静态方法,实例方法和虚方法。必 须要指定调用方法的类型(对于静态方法) 或者对象(对 于实例方法/虚方法),如果在该类型/对象中

46、找不到该方法,会检查其基类来匹配方法。另一个是 callvirt,用来 调用实例方法和虚方法,不能用于调用静态方法。必须要指定调用方法的实例对象,如果这个对象为 null,会抛出 NullReferenceException 异常,这意味着每次调用前都会有 额外的null 检查,从而比调用 call 慢一些。如下代码所示:public sealed class Programpublic Int32 GetFive()return 5;public static void Main()Program p = null;Int32 x = p.GetFive(); /在 C#中,使用 callv

47、irt,会抛出 NullReferenceException 异常在 C#编译器中,使用 callvirt 调用所有实例方法 (包括虚方法),使用 call 调用所有静态方法。 对于其他的编译器,这一点不能保证,所以在虚方法和非虚方法之间改动而不重新编译,会产生无法预测的问题。C#使用 call 而不用 callvirt 调用虚方法的特例:ToString,见下:internal class SomeClasspublic override string ToString()return base.ToString();这时候,生成 call 的 IL 代码。因为如果使用 callvirt,意

48、味着这时一个虚方法,从而递归执行该方法,直到 AppDomain 的堆栈 溢出。在调用值类型定义的方法时,使用 call。这是因 为,首先,值类型是密封的,从而不存在虚方法;另外,值类型永远不会为 null,所以永远不会抛出 NullReferenceException 异常;再者,如果使用 callvirt,就要使用装箱机制,性能会有极大影响。在设计 class 的过程中,要尽量少定义虚方法。取代办法:可以定义一组重载方法,经其中最 复杂的方法虚拟化,而将所有有用的重载非虚拟化,示例如下:public class Setprivate Int32 m_length = 0;/这个有用的重载是

49、非虚 拟的public Int32 Find(Object value)return Find(value, 0, m_length);/这个有用的重载是非虚 拟的public Int32 Find(Object value, Int32 startIndex)return Find(value, 0, m_length - startIndex);/功能最丰富的方法是虚 拟的, 可以被重写public Int32 Find(Object value, Int32 startIndex, Int32 endIndex)./具体实现sealed 密闭类尽量使用。将 sealed 改为非密闭 的容易,反之困难;性能

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 大学课件

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报