CLR笔记

第一步用对应语言的编译器(针对运行时)生成相同的PE(+)文件;
其内含有: 
 a. PE or PE+头:pe运行于32或者是64位机,pe+只运行于64位机(可以模拟WoW64运行32位机程序),头中含有文件的类型信息(GUI CUI DLL)及文件编译的时间,如果托管模块只含有IL代码,则pe头则被忽略,如果托管模块含有本地cpu代码,那么这个pe头还有关于本地代码的信息;(可以通过DumpBin.exe和CorFlags.exe来检查托管模块中的头信息)
 b.CLR 头:包含编译这个托管模块所需要的必要信息:比如:兼容的CLR版本,一些标志位及含有entry point的元数据的方法定义表,托管代码的元数据的大小及位置,可选的强名等;exact format of the CLR header by examining the IMAGE_COR20_HEADER defined in the  CorHdr.h header file.?38
 c. metadata :每一个托管代码都包含元数据表,主要有两种表:1.在你的源代码中自己定义的类型及成员表;2.被你引用的其它的类型及成员表;
 d. IL code  (托管代码) :编译器编译源代码产生的代码,在运行的时候CLR将即 jit为本地cpu代码;
这些统称为托管模块(可集成至程序集的组件可以用多种语言写的部分);
metadata与il code 的生成:
 编译器同时产生元数据和IL代码并将其混合放入托管模块,也就是metadata被当作代码一样放入exe,dll文件当中,使得托管模块不能进行逆向生成;
元数据的作用:
 a.元数据使得代码在编译的时候不再依赖于头文件或者是库文件;
 b.vs 使用元数据来实现自动识别;
 c.CLR使用元数据来保证你的代码只进行安全操作;
 d.元数据允许一个对象或其字段序列化至内存,传至远程机器,再反序列化重新在远程机器上生成对象;
 e.元数据允许垃圾回收器跟踪对象生命周期,垃圾回收器可以判断出对象的类型,然后查询对应的元数据来确定这个对象还引用了其它的一些什么对象;

程序集是一个或者是多个模块(及资源文件)的逻辑组合,是一个最小的重用单元(安全,版本),根据你的编译选项(AL.EXE?)可以生成单文件或多文件的程序集;程序集文件含有一个另外的元数据表(Manifest),它描述了所有组成文件之间的关系.

CLR的版本检查:命令行调用clrver.exe可以显示所有本机上已经装的CLR版本及固定进程的版本等选项;
csc提供了一个 /platform 选项来生成针对特定cpu结构的程序集,默认为anycpu;
针对IL代码,有ILAsm.exe ILDasm.exe来分别编译其为程序集和反编译其为il代码;(没有元数据如何编译为程序集?)

IL代码的执行流程:检查待用的代码中的所有引用类型,加载类型结构(在LH上)及方法表,表中每一个地址都指向方法实现的入口地址(CLR内部),调用时将根据cpu结构jit为本地代码(动态内存上),将其入口地址返回方法表中,再次调用的时候直接跳转到动态内存中的本地代码中;

jit本地代码流程:在实现类型的数据集的元数据中查找被调用的方法获得方法的IL代码,分配一块内存将IL代码编译为本地代码并保存内存地址至方法表中;
问题是:方法表中的入口地址指向的是CLR内部的方法地址,为什么又要到元数据中来查询对应的IL代码?这个方法表里面的地址到底是指向哪里;实际上加载类型的时候已经读入它的元数据了。?

优化代码:两个选项/optimize /debug 三种情形:
- ,-(默认);il不优化 ,本地代码优化;
- ,+/无/full/pdbonly;              il,本地代码均不优化;
+ ,-/+/无/full/pdbonly;       il,本地代码均优化;
vs 中的debug 为/optimize- /debug:full
release 为/optimize+ /debug:pdbonly
注:当产生不优化il代码的时候,c#编译器会自动加入空操作(nop)指令,使得断点中断变得容易;

包含不安全的代码在运行的时候jit会检查程序集是否有System.Security.Permissions.SecurityPermission Flag's
SkipVerification标记(csc 选项中的unsafe选项),如果没有则不能运行;用peverify.exe 来检查程序集中的所有方法是否含有不安全代码。

IL代码的保护措施:a.服务器应用程序都是不部署程序集,别人无法获得你的程序集;
  b.采用第三方软件来将名称混合打乱,但是效果不大因为生成的名称要能被编译;
  c.关键算法采用非托管代码来写然后与托管代码相集成;

本地代码生成器:代码在安装时期被编译。
应用情形:
1.提高启动时间。
2.降低应用程序的工作集。(可以映射至多个进程或appdomain而不需要复制)
保存的目录为$windows.~q/data/windows/assembly/nativeinages_..... ,CLR装载程序集的时候,会到此目录下看是否有对应的本地文件。
潜在的问题:
1.没有代码保护,运行时CLR可能会由于反射序列化等原因来重新查找程序集的元数据;
2.不能及时根据程序集的变化进行更新;
3.不能于基址对齐;
4.不能产生针对CPU结构的优化代码;
总结:不能节省多长时间,反而会造成很大不变;

多语言集成:在.NET中,CLS是所有支持运行时必须支持的最小特性集合,而CLR/CTS是整个运行时的最大集合(il语言的所有特性),用已支持尽量多的语言。总之一个是所有语言的子集一个事所有语言的超集;语言之间的互操作性只能通过CLS来实现;
[assembly:CLSCompliant(true)]针对整个程序集,只要是暴露在程序集之外的就必须要能被其他的语言访问。例如任何在类中的成员只有字段和方法,这是所有语言都支持的;

与非托管代码的互操作:31?
1.托管代码调用DLL中的非托管代码
2.托管代码调用已经存在的COM组件
3.非托管代码可以使用托管类型

DLL文件的缺点:
1.版本问题,升级带来的不兼容其他程序;
2.安装卸载问题,不能干净的卸载,简单的移植;
3.安全问题;

生成程序集:/t[arget]:exe, /t[arget]:winexe, or /t[arget]:library.
生成模块:/t[arget]:module.  生成文件后缀为是.netmodule. 标准的PE文件,vs不支持创建多文件程序集。
响应文件:以.rsp(response)为后缀的文本文件,其中包含一系列的csc开关选项,来进行一定条件的编译,在编译时,你要用@+文件名来指定你的响应文件;编译器支持多个响应文件,有冲突时命令行的csc选项优先覆盖本地响应文件,本地响应文件覆盖全局响应文件(csc.exe同目录下的csc.rsp);
当使用/r:命令时,可以显示指定引用程序集具体路径,
若无引用路径(同上csc.rsp):则
a.当前目录;
b.csc.exe的本地目录(CLR所有dll就在这个目录中);
c.被标记为/lib或LIB的目录;?38
可以使用/noconfig选项来明确指定开关而不管任何响应文件;
注:这里找到的系统引用文件一般都是在b中,b中的clr dll做为生成程序集提供便利,否则要到Gac中查找所引用的程序集,实际上运行时就是在Gac中调用引用系统文件的;

元数据:0x01=TypeRef, 0x02=TypeDef, 0x23=AssemblyRef, 0x26=FileRef, 0x27=ExportedType.
包含三种类型的表:定义表,引用表,还有manifest表;
定义表:

  1.ModuleDef  含一个对应于此模块的项,此项包含模块名及其扩展名,和模块版本ID;
 2.TypeDef   每一项对应于模块中定义的一种类型,此项包含对应类型的名称,基类,访问修饰符和它所拥有的在MethodDef 中定义的方法下标,在FieldDef中定义的字段下标以及在PropertyDef和EventDef中定义的下标;
 3.MethodDef每一项对应于模块中定义的所有方法,此项包含方法名,修饰符,签名以及其il代码在模块中的偏移地址,每一项还指向ParamDef 中的项来寻求更多的参数信息;
 4.FieldDef 每一项对应于模块中定义的一个字段,此项包含访问修饰符,类型,名称等;
 5.ParamDef   每一项对应于模块中定义的一个参数,此项包含访问ref ,out ,类型,名称和应用于参数上的属性(in,out等)等;     
 6.PropertyDef每一项对应于模块中定义的一个属性,此项包含访问修饰符,类型,名称等;
 7.EventDef 每一项对应于模块中定义的一个事件,此项包含访问修饰符,名称等;
引用表: 1.AssemblyRef 每一项对应于模块中引用的一个程序集,包含了程序集的具体信息(名称,版本号,文化,公钥hash过的逆8位);
 2. ModuleRef 每一项对应于模块引用的一个PE文件(模块文件),包含模块的名称扩展名,这个表通常和此模块中的类型相联系;
 3.TypeRef 每一项对应于模块引用的一种类型,包含了类型名和Type的实际地址;
 4.MemberRef  引用的所有成员,每一项含有成员名及签名和指向定义其的TypeRef项;
manifest表:
 1.AssemblyDef 为这个作为程序集的模块保存一个项,包含了程序集的名称,版本,文化,公钥等;
 2.FileDef 除了自己为每一个PE文件和资源文件保存了一项,包含了文件的名称,扩展名,hash值;
 3.ManifestResourceDef 为此程序集的每一个资源文件保存了一项,包含了资源名及是否程序集外可见;
 4.ExportedTypesDef 为引用程序集的模块文件中所提供的类型保存一项,包含了类型名及包含此文件的PE名(指向2),和TypeDef表索引。

CLR加载程序集:它优先加载包含manifest元数据表的文件,然后通过manifest中的数据来获得程序集中其他文件的名称,程序集的三个显著特点:1.程序集定义了最小的重用单元;2.有版本信息;3.可以有安全信息相联系;
一个程序集可以使单一的一个文件,还可以使多个PE文件和资源文件的逻辑组合;建立一个程序集你必须选择一个PE文件作为manifest表的持有者,或者你可以创建一个只含有manifest表的PE文件;
推荐使用多文件程序集的理由:
1.可以分开在需要时下载,可以部分打包安装(程序的部分功能);
2.可以添加任何形式的资源文件作为程序集的一部分,而不需要将资源文件添加到源代码中;
3.将不同语言实现的部分均编译成PE文件(module),然后可以反汇编成IL代码;
在vs中的.net选项中添加引用选项HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\AssemblyFolders
\MyLibName    创建自己的引用目录并将其值改为实际目录路径;

程序集链接器(Al.exe):当所用的编译器不提供类似/addmodule的选项时使用,产生一个只含有manifest的程序集;局限性:只能链接两个模块文件成为第三个程序集文件(一般为库文件)。如果是exe,winexe文件则要具体指定入口;
资源文件:csc .exe中的/res:选项,可加入任何资源文件;

版本规则:
Major Number  
Minor Number       公开的版本
Build Number         创建的总次数?
Revision Number    重创的次数    ?
版本信息辨别:AssemblyFileVersion,AssemblyInformationalVersion,AssemblyVersion
第三个信息是最重要的体现在:被存储在AssemblyDef(manifest元数据表),用这个信息来唯一的标识一个程序集,当此程序集被引用时,这个版本信息被放入对方的AssemblyRef表中;

文化:程序集没有被指定文化信息则被默认为culture neutral,一般来说要建立一个有特定文化资源的程序集,都是先建立一个只包含代码和少量的必须资源的程序集(文化中立),再建立有特定文化而不含有代码的程序集将其赋予特定的文化信息,最后在引用核心程序集即可;(必须为每一个你想支持的文化信息单独建立一个程序集又称卫星程序集)
如何生成文化程序集:空.cs+资源文件+csc+al(指定文化);
如何部署文化程序集:放置于程序目录的以文化字符串为名称的子目录中,运行时你可以通过System.Resources.ResourceManager访问到它的资源文件;

配置文件(configuration):60?85?88?
配置文件详解:
配置文件名及位置是根据执行文件的类型来决定的,exe则配置文件必须位于程序基目录且名字为程序名+exe+.config;如果是ASP.NET Web Form ,文件必须位于程序的虚拟根目录且命名为Web.config ;
1、指定子目录
<configuration>
 <runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
   <probing privatepath="x;y;z"/>
  </assemblyBinding>
 </runtime>
</configuration>
注:指定所需要的程序集的子目录,若是强名程序集时,则GAC-codebase,只有当code元素没有指定时才查找私有路径,若是弱名则直接查找子目录;
2、绑定程序集策略
<dependentAssembly>                                                                  (为每一个程序集使用一个dependentAssembly元素)
<assemblyIdentity name="name"  publicKeyToken="1f2e74e897abbcfe"   culture="neutral" />
<bindingRedirect     oldVersion="3.0.0.0-3.5.0.0"  newVersion="4.0.0.0" />
<publisherPolicy apply="no" />                                                    //运行库是否应用程序集出版商策略      
<codeBase version="4.0.0.0"   href="http://www.wintellect.com/JeffTypes.dll" />
</dependentAssembly>

弱名于强名程序集及其私有和公有部署:弱名仅可以私有部署,强名两者都可以;可以说强名就是用来解决dll hell 发生的原因,公有部署。

程序集唯一标识的四要素:名称,版本,文化,公钥(不是公钥标志);System.Reflection.AssemblyName 可以设置或获得相应的信息,如属性CultureInfo, FullName, KeyPair, Name,and Version;
           方法GetPublicKey, GetPublicKeyToken, SetPublicKey, and SetPublicKeyToken;

使用SN.exe 获得钥对: SN  -k  filename.keys (可以文件名及扩展名不同)  
导出公钥文件:SN -p  filename.keys  filename2.publickey
显示公钥:   SN -tp filename2.publickey。公钥标记是公钥SHA1hash码的后8为逆序;
(SN.exe 不提供显示或导出密钥)csc中的一个选项/key-file:filename.keys就可以使程序集具有强名属性,整个加密过程如下:在生成程序集的时候,程序集的manifest表中包含了程序集所包含的所有文件的列表,将所有文件内容hash过后同文件名一同写入FileDef表,(此处采用默认的SHA1算法,可变),然后将整个PE文件用SHA1进行hash(不可变),接着将hash值用私钥加锁为RSA数字签名并存放在不被纳入hash计算的PE文件的预留位置中。最后将公钥加入manifest的AssemblyDef表中。当被程序集引用的时候,即遇到/r:选项,编译器会将每一个引用程序集的名称,版本,文化(idasm中的Locale)及公钥信息(标记)放入元数据中的AssemblyRef表项中去。

强名的作用:当被安装入GAC时,首先用公钥解密RSA获得PE文件的hash值,在比较PE文件的hash值,比较两者的hash值,如果相等则PE文件没有被动过,然后再依次hash所有的文件与程序集FieldDef表中的hash值相比较,如果有任何一个文件值不等则出错安装失败。

公用程序集缓存:(GAC在C:\Windows\Assembly中)意图是解决两个同名程序集不能放在同一个公共目录下的问题(它使得同一程序集的不同版本统一放置),使用GACUtil.exe工具添加(i或直接拖拽至目录)删除(u)强名公共程序集,GAC的内部目录结构是为了维护程序集强名和它所在的子目录的关系,也就是说可以通过一个函数以强名为参数就可以返回隐藏的程序集文件,添加程序集时同时也添加了它所依赖的所有模块;
在cmd下可查看具体目录(dir)。其下5个子目录表示的是应用于各个cpu结构的程序集。运行的时候CLR根据要引用的程序集的强名来获得GAC的子目录下的程序集。如果没有,CLR则在本地目录,配置子目录下寻找,且每次加载都会进行GAC下的验证过程。GAC是根据强名加上cpu结构来唯一确定一个被调用的程序集,而CLR可以根据这个程序集现在在运行的cpu结构来确定GAC中的程序集;

注:公钥标志和公钥的问题:当CLR根据AssemblyRef中的信息来到GAC中定位时,并不知道公钥只知道公钥标志,那是如何唯一的确定一个强名程序集。还有绑定的时候是怎么定位的,csc选项是通过单纯的程序集名来绑定的(csc.exe中的GAC dll可能只有一个版本如果有多个版本呢?),绑定时一般有特定的版本来引用,若要改变绑定版本则需要修改配置文件。


延迟签名:(部分签名)在开发阶段,可能此程序集要被别的程序集依赖,但是可能这个程序集还不完善,还要修改它的部分文件,如果使用的是强名方法则每一次都要重新加载文件(加密),并重新生成程序集。如果使用的是延迟签名则可以直接修改模块文件而不需要重新生成程序集。
执行步骤:导出公钥,csc中加/delaysign 选项来编译程序集,编译器会将公钥(标记)加入AssemblyDef中去。
    csc /keyfile:My.PublicKey  /delaysign MyAssembly.cs
加载至GAC:正常的加载方式会出错,因为它没有执行文件验证。
1.SN -Vr    可以让系统运行时告诉CLR不执行组成文件的验证;       SN.exe -vr MyAssembly.dll
2.发布时重新加密;                SN.exe -R MyAssembly.dll My.Key
3.可加载至gac;
4.取消验证;                             SN -Vx SN-Vu 可以让测试的机器移除验证跳过的特定项或者所有项。
还有关于密钥的保护的密钥容器,较好掌握;

CLR是如何处理类型引用的:最初加载CLR及初始化,然后CLR读取程序集CLR头找到入口地址(Main在CLR头的MethodDefToken中),根据MethodDef表中的地址找到方法的IL代码,然后JIT为本地代码并执行;
关键在于如何定位特定类型的位置:找到成员所属类型的位置后在加载此类型至Load heap中。

关于元数据表的内容以下作简要说明:
0x01=TypeRef, 0x02=TypeDef, 0x23=AssemblyRef, 0x26=FileRef, 0x27=ExportedType.
在ILDASM反汇编之后的代码中可以有三种表符:
1.MemberRef 在il的显示字节中可以发现il代码都是直接调用这个表的表符,这个表符划分的最细;0x0a
2.TypeRef 这个表符指这个类型在元数据全局引用类型中的位置;0x01
3.AssemblyRef 指向引用程序集在元数据全局引用类型中的位置 ;0x23
4.ResolutionScope    指向此类型所指向的引用程序集(3);
流程如下:IL代码找到1,再由1到2,再到4,最后到3,找到所引用类型所属的程序集。如果在此程序集的模块中的话则根据元数据加载对应的文件;如果是typedef内的成员则均已经被标记为 RVA。
注:凡是注明RVA标记的,都是能够直接定位IL代码的成员即可以直接执行;

凡是在IL代码中的引用类型无非以下三种情形:
1.同文件内的,即此引用类型是同程序集的同一个文件中的,那么可以直接根据RVA地址执行;
2.同程序集不同文件,当然这个文件肯定在程序集的manifest表中引用了,CLR直接到程序集的目录中去找对应的文件,并执行hash验证,最后根据元数据找到类型RVA并执行;
3.不同程序集(不同文件),执行上面提到的流程。

命名空间和程序集,一般而言程序集是实际上的物理结构而命名空间是逻辑结构,程序集可以以命名空间来命名但绝大多数两者都没有什么关系。

CLR的类型系统,任何对象的头部都有两个指针,一个是指向同步进程块,另一个是指向Type Object ,当调用GetType的时候只是简单的返回这个指针的地址,然而,Type Object也是Type的对象,前者同样指向后者,而后者指向自己,因为自己就是自己的一个对象。然而CLR默认将第二层封装起来所以无法访问得到类型的元类型。

原始类型都是由编译器提供类似 using int = System.Int32 的映射,文章建议直接使用类名来声明;
1、如string和String两者之间另人疑惑,其实两者之间由编译器连接其等价关系;
2、对于在不同的平台下的工作人员,int 到底是多少位会产生疑惑;
3、FCL中提供了许多方法名类似于ToInt32, ToSingle等,会使得代码更加易于读;

关于checked,unchecked 会检验代码内有没有出现溢出现象,对内部调用的函数没有作用,它的整个作用是让编译器决定用运算符的.ovf 版本,还是默认的不检查版本。有两种策略:显示的范围内是按照标识符来确定,而不是显示的则默认按照编译器的设置/checked 。注意两种情形
1.b = checked((Byte) (b + 290));会抛出异常
2.b =   (Byte) checked(b + 200);不抛出异常
Decimal类型在CLR中不是原始类型,所以没有专门的IL代码来实现其操作,只提供了方法调用来满足Decimal在c#中的原始类型假象;

CLR的类型内存布局:
首先了解两个特性:1、System.Runtime.InteropServices.StructLayoutAttribute (LayoutKind.Auto/Sepuential/Explicit)
Auto 指示采用CLR认为优化的方式(类的默认布局方式),Sequential指示采用以定义时的原有布局来布局(结构体的默认布局方式容易被非托管代码来访问),Explicit 见下。
2.System.Runtime.InteropServices.FieldOffsetAttribute        显示指定字段的偏移地址;
[FieldOffset(0)] Byte b;  // The b and x fields overlap each other
[FieldOffset(0)] Int16 x;  (参数为具体的偏移字节数)此处类似为c++中的联合;

结构体在c++中默认为私有但是在c#中必须显示的指定它为public 才能直接“.”访问;
在调用Console.WriteLine(+)时,默认只接受string型,会默认调用下面的函数:
public static String Concat(Object arg0, Object arg1, Object arg2);  它的内部又会默认调用每个对象的ToString()

预处理器指令解析:
# define      或/d:debugA 编译选项   , #if  debugA doa  #elif debugB dob (可选)  #else doelse  #endif 
#undef        移除#define选项
#warning    人为的生成一个警告,一般应用在Debugging中来提示正在运行在Debug中;
#error         人为的生成一个错误;
#line            模拟行号,#line hidden #line default 之间可以对调试器隐藏;
#region       #endregion   可以展开折叠代码块(大纲显示功能)
注:其可以和#if块嵌套,但是不可以部分重叠;
#pragma 
#pragma warning 
#pragma checksum   见MSDN

关于值类型及引用类型的几个能调用函数的区别:
尽管是值类型但是同样可以直接调用继承至基类的虚方法(Equals, GetHashCode, ToString),因为System.ValueType重载了所有的虚方法,然后重载的方法要求this是一个值类型,因为值是密封类型所以CLR可以以非虚的方法来调用虚方法。当调用非虚方法(GetType,MemberwiseClone),时则要装箱才能够调用;
此处类似于:ValueType是一个壳,它的所有实现就是它的子类的实现。

如果将一个值装箱后想改变装箱后箱内的值,除了将装箱的变量转换为值类型所实现的接口,再调用接口方法才能实现这个目的;

类型判等的一些规则及建议:
指导规则:自反,传递,闭包;
模版方法:1.参数不为空;
2.this和参数.GetType()类型要相等;
3.比较这个类的内部数据要相等;(valuetype使用的是反射的方法)
4.调用基类的Equals() 如果返回flase则返回false,不然返回true;
注:由于Object类默认不是按照上面的模版方法来实现的,而是直接按照是否是同一个引用(==)来实现的,所以补救的方法是,在离Object最近的那个方法上直接返回true而不是调用Object的Equals方法,这样就可以解决这个设计上的缺陷,但是这样就没有了实例上调用引用相同的方法了,为弥补这一缺陷,ReferenceEquals的静态方法;
关于类型判等还有可能要实现的几个设施:
1.System.IEquatable<T> 一般实现这个接口,然后在Equals内部调用这个接口方法,来实现类型安全;
2.重载==,!=    其内部调用类型安全的Equals方法;

hash值:在类型定义中,如果你重载了Equals方法则你必须要重载GetHashCode方法:
因为System.Collections.Hashtable 和 System.Collections.Generic.Dictionary要求两个对象要是相等则它们必须含有相同的hash值。它内部的实现机理是:当你加入一个键值对时,首先获得键的hash值,这个值来指示这个值键对应该存放在哪一块内存,当获取一个键值对的时候,首先先获得指定查找的键的hash值,根据值所指向的内存块来顺序搜索相等的键值,这样就意味着如果你在表中直接修改了表的键值话,那么你就无法在找到这个键值所对应的对象了。

类的可见修饰符:public ,internal 前者是都可见,后者是程序集可见;?未成功,158
友程序集:System.Runtime.CompilerServices.InternalsVisibleTo,
例:[assembly:InternalsVisibleTo("friendassemblyname,  PublicKey=12345678...90abcdef")]

静态类在IL代码中表现出一个abstract 和sealed两个修饰符;
部分类,结构,接口(仅由c#编译器提供的特性):将同一个源代码文件划分至不同的文件中去,关键字partial 必须显示指定两个文件,不同文件中的源代码必须要用同一种编程语言来实现,因为CLR对其一无所知;
重载函数中c#中不允许以返回值来判断重载,但是CLR支持(IL),在c#中关于类型转换中可以默认以返回值来判断重载;

CLR调用(非)虚函数机制:
1.call :用来调用静态,实例,虚函数。特点:调用实例或者虚函数时,它不会检验变量是否为空。其经常用于以非虚的方法来调用。
2.callvirt:用来调用实例或者是虚方法不调用静态方法,当调用虚方法的时候,CLR先找到变量的实际类型,它会检查变量是否是空。
c#编译器默认用call来调用静态函数,而用callvirt来调用实例和虚函数(必须要检查是否变量为null),但是在一些情况下也会发生一些交叉调用:例如:1.在调用基类函数的时候,如果仍然用callvirt来调用的话,那么就会出现递归调用,会抛出异常(即使用的是base关键字);2.当调用值类型定义的函数的时候就会调用call,因为值类型默认就不会发生null异常(有默认值),且是密封的不会发生多态;
虚方法设计原则:如在Console.WriteLine()中如果其为虚函数的话,那么不同的版本之间应该集中调用一个共同的虚函数,而其他版本的函数则内部调用虚函数(类似于设计模式中的模版方法);

推荐的类型设计原则:默认采用selaed

常量:const
当定义一个常量类型的时候它的值必须在编译期被确定,编译器将它的值保存在程序集的元数据中(只能是原始类型);是静态类型,但是由于它是直接保存在元数据中的,所以它没有跨程序集来引用的步骤,这就导致了所引用程序集中的const改变了,但是引用它的程序集在没有重新编译的时候不会访问而是直接到自己的元数据中来查找这个const类型;(条件是次程序集没有启动强名)
字段:fields
忽略点:volatile 指示一个字段可以由多个执行的线程修改;?见线程同步;
因为字段是存储在TypeObject或者是Object中的动态内存,值是在运行时获得的,所以不会出现const中的不一致问题;
用static readonly 字段来代替const;
readonly 只可以在三个地方来赋值:1.构造函数;2.简单初始化语法;3.反射?
如果readonly指向一个引用类型,那么它所指向的那块托管内存不准变,但是托管内存中的内容可以任意变化;

类的构造函数的一些规则:
不可继承,
如果没有显示定义构造函数那么编译器会生成一个默认的无参构造函数(简单的调用基类的无参构造函数!
        此时如果基类没有无参的构造函数,编译器会产生编译错误);
如果是一个抽象类,那么编译器会默认产生一个protected型的构造函数,否则为public型;
如果基类没有提供无参的构造函数,那么派生类构造函数必须要显示的调用基类的构造函数;
如果是静态(sealed and abstract)类,则不会添加默认的构造函数;

构造函数中的初始化:编译器提供的简单初始化语法,会将代码默认加入每一个调用的构造函数之前进行执行,如果不同的构造函数有不同的构造方法,那么将其封装在一个无参构造函数中去,然后可以用别的构造函数(this())来调用这个常用构造函数;

结构体的构造函数:
c#不允许值类型定义无参构造函数,CLR允许,因为没有无参的构造函数,所以结构体不能用初始化语法执行初始化。 如果显示定义含参构造函数的话则必须要初始化所有的字段,否则会编译错误;

静态构造函数:(有且仅有一个且不能含有参数,仅有static一个修饰符默认为私有)
在加载一个类型的时候,会检查其是否有静态构造函数且是否在当前应用程序域已经执行过一次,且CLR来保证其线程安全;且类型的静态构造函数没有固定的执行顺序,如果CLR抛出未处理的异常,代码再调用类型的任意字段或者方法都会抛出System.TypeInitializationException 异常;
静态字段的初始化和静态构造函数的调用顺序跟非静态的初始化语法及非静态构造函数相同;
性能:当添加静态构造函数的时候,JIT必须要知道在哪里添加这个调用;
1.precise 语义:将调用添加在第一次创建类对象或者访问类的非继承字段及其成员之前,CLR会在精确的时间来调用能够静态构造函数;
2.before field init  在访问非继承静态字段之前,CLR保证静态构造函数会在静态字段访问之前来调用,可以提前来执行;
两者之间的区别:见195;,如果没有定义静态构造函数,而直接初始化静态字段则c#编译器会默认在元数据中为此类型前添加BeforeFieldInit 标记,如果显示的定义了一个静态构造函数的话,那么则去除此标记在准确的时机来调用静态构造函数,用这个标记来说明这个类型的具体策略;

运算符重载:在CLR中,运算符重载就是简单的方法定义;
规则:public static  returntype  operator+();在参数或者是返回类型中至少要有一个定义的类型;
执行过程:CLR创建一个方法 且有specialname标记(说明此方法是一个特殊的方法),当编译器遇到一个+时,会寻找任何一个操作数是否定义一个有specialname标记的op_Addition方法;(此时如果方法定义冲突编译器会显示调用不明确的编译错误),而FCL中的原始类型均没有重载,因为它们的运算符都是直接il专门的指令来执行的;
类型转换:public static implicitly/explicitly operator targettype (source type );]
il生成op_Explicit/op_Implicit  两类以此为方法名的方法,这里允许CLR根据返回值类型(op_Explicit),来重载同名同参函数; 示例:Decimal
out/ref的区别:CLR默认方法是按值传递,用ref /out就指出要以引用传递,实际上除了编译器检查的初始化问题,它们产生的il代码是一样的(代表一个指针),所以不可以这两个关键字来区分重载可以和别的方法重载;
可变参数传递:param
规则:必须位于最后,且是一维的数组,调用顺序为先调用最匹配的无paramattribute的方法,如果没有则找相匹配的此方法;

属性:无参属性,有参属性(索引器)
CLR中并不区分有参和无参属性,它们只是一对方法及一块元数据。
属性可以没有backing field ,定义一个属性一般会有2~3项:get _+属性名/set_+属性名  方法,属性在元数据中的定义;(这一项包含一些标志及属性的类型,且指向get/set方法,这一信息用来将属性这个抽象概念与具体的方法联系起来ProperInfo)

索引器至少含有一个参数,这些参数可以是任意的类型(除void ),语法是 this[...]表明c#只有对象的实例才可以有索引器,但是CLR允许静态有参属性;il实现其方法名为get_Item/set_Item ,只要检查此类型有没有Item 属性就可以知道其有没有实现索引器(list<>中的Item);
改变索引器的名称(string中的char):System.Runtime.CompilerServices.IndexerNameAttribute("newname"),在c#中不可以通过修改名称而参数集相同来重载索引器,可以修改名称但是整个类型中只能有一个统一的名称,且可以根据参数集的不同来进行重载;

定义事件的几个过程:
1.定义一个应该发送给接收者的事件参数  (继承自EventArgs),包含例如发送方接收方名称等数据;
           如果定义一个没有额外信息的事件参数直接可以调用 EventArgs.Empty;
2.定义一个事件成员,public    event    委托类名     事件名;
3.定义一个方法来触发事件,这个方法一般以事件参数为参数,检查event是否为空(有无注册),再调用事件
事件名(this,args);
注:一般将其定义为 protected virtual  使得事件可以被自己或者是子类触发,采用将事件赋给一个暂时的委托类型通过委托来触发这个事件链(保证线程同步);因为委托为空时,不会抛出null异常 ,而事件为空的时候则会抛出null异常;  
4.收集触发事件的相关信息从而传递给第三个函数来触发相应的事件;

事件的内部实现机制:
public event EventHandler  NewMail;
c#编译器将其翻译成
 1. private EventHandler NewMail = null;                  
事件委托链的头的引用,为防止链被操作故其为私有,仅能通过下面两个函数来进行操作;
 2. [MethodImpl(MethodImplOptions.Synchronized)]                              操作为线程同步
    public void add_NewMail(EventHandler value)                           方法名为add_+事件名
  {
   NewMail = (EventHandler) Delegate.Combine(NewMail, value);           返回一个新的链头的引用
  }

 3. [MethodImpl(MethodImplOptions.Synchronized)]
    public void remove_NewMail(EventHandler value)                              访问修饰符于事件声明符相同
  {       (static,virtual,public等)
   NewMail = (EventHandler)Delegate.Remove(NewMail, value);
  }
4.定义事件的元数据项,包含了一些标志及所使用的委托类型 和以上两个方法的引用,其作用就是连接事件这个抽象概念和所定义的方法。
注册事件过程:一般在响应类体中进行注册+=,编译器将其翻译为value.add_eventname (new handler  (this.method) )
用一个新的委托包装一个方法,同理于 -=;

事件中的线程安全:[MethodImpl (MethodImplOptions.Synchronized)]属性标志操作同一个实例的(包括静态)事件,这个方法在同一时间只能被执行一次,缺点:
这个属性用于实例方法的时候,CLR用此对象来做线程同步的锁,这样如果这个类中定义过多的事件则会影响性能;(较少),但是锁会向所有的代码暴露;这就意味着任何一个人都可以对其加锁,会发生死锁现象;
这个属性用于静态方法的时候,CLR用类型对象作为线程同步锁,如果这个类定义了过多的静态事件,所有的事件方法都用同一个锁,影响性能;(较少)且会破坏应用程序域之间的独立性;
解决事件的同步问题的方法:

 internal class MailManager {

 private readonly object m_eventLock = new Object();                                          //如果事件为static则其也要做相应的改动

 private EventHandler  m_NewMail;

 public event EventHandler  NewMail {

 add       {

  lock (m_eventLock) { m_NewMail += value; }

              }

 remove {

   lock (m_eventLock) { m_NewMail -= value; }

             }

                                                          }

 protected virtual void OnNewMail(NewMailEventArgs e) {

  EventHandler temp = m_NewMail;                             //保证不抛出null异常线程安全,赋予私有委托链头

  if (temp != null)      temp(this, e);

                                                                                            }

 public void SimulateNewMail(String from, String to, String subject) {

  NewMailEventArgs e = new NewMailEventArgs(from, to, subject);//构造事件参数;

  OnNewMail(e);

                                                                }

 }

 

定义多事件类型时,除了显示的声明其访问函数,还可以采用懒惰式创建委托即要被注册的事件其委托才会不分配内存;

Char的相关操作:
1.提供两个只读属性:MinValue  (/0),MaxValue(/uffff);
2.提供一个静态函数GetUnicodeCategory( char) 来返回当前char 的类型(UnicodeCategory枚举);
          同样char提供了一系列的静态方法  Is~(),其中也是内部调用其上的静态函数
3.提供转换大小写的操作:静态方法
两类:ToLowerInvariant和ToUpperInvariant 文化不可预知的转变;(不推荐)
            ToLower和ToUpper 默认以线程文化来处理,但可以显示指定CultureInfo;
4.于string的转换:静态方法
   Parse/TryParse                                        ToString
5.比较字符:实例方法
   Equals 判断是否代表相同的16位Unicode                       CompareTo(非文化敏感的)
6.
     A    GetNumericValue                                   返回与Unicode字符相对应的数值;
     B    a.直接强制转换 ,会直接生成相对应的IL代码,效率最高;
           b.Conver 类:提供了几个静态函数,为checked 状态;
           c.IConvertible 接口:char 和所有值类型都实现了此接口;

string类: (换行 Environment.NewLine)
@符号的使用来保证string中没有转义字符串;verbatim string
特性:不变性,一旦创建就不会再被改变;(不会出现线程不同步问题)

枚举类型comparisonType 详解:
CurrentCulture = 0,                                                       //与文化相关的(当前线程文化)
CurrentCultureIgnoreCase = 1,
InvariantCulture = 2,                                                     //一般不用
InvariantCultureIgnoreCase = 3,                                   //一般不用
Ordinal = 4,                                                                  //普通程序中比较
OrdinalIgnoreCase = 5
ToUpperInvariant        ToLowerInvariant          转换大小写,后者推荐;

System.Globalization.CultureInfo 来代表一个 语言/国家 对,在CLR中,每一个线程都有两个文化信息与其相联系
1.CurrentUICulture   主要用于获得向终端显示的资源;
2.CurrentCulture  用于数字,时间格式化,比较等各种操作;
一般两者相同,一个CultureInfo 代表了一个文化的字符对照表,每一个文化只有一个;

字符串共享机制:(由于字符串的不变性,所以为节省内存只保留一份)
当CLR初始化的时候,会在其内部创建一个hash表,key(string)/values(string在托管堆的引用),
获得str的hashcode然后在相应的内存块查找,找到相应的string获得引用
public static String Intern(String str);     若有则返回,若无则创建;  
public static String IsInterned(String str);若有则返回null;
整个hash表在AppDomain卸载的时候才能释放回收,
String Pooling  即在同一个元数据中仅保存一份相同字符串;

string中的字符和文本元素:
字符可以直接用对象的实例函数来调用,char是一个代表单独的16位Unicode代码,而有时候一个Unicode代码由连个字符组成,而有的以两个Unicode组成一个元素组成,则需要用stringInfo来调LengthInTextElements元素长度
SubStringByTextElements 获取部分元素;及几个静态函数来操作文本元素;
string的其他常用操作:Clone() ;    static  Copy();复制一个相同的string , CopyTo();         SubString();      ToString();

动态string;(System.Text.StringBuilder)
它的内部维护着一个指向char型的数组,可以通过函数来操作这个数组,如果默认分配的大小不够则自动重新分配,先前的被gc,当完成操作后可以通过ToString来返回一个string类型,这个函数只是简单的返回一个它内部维护的一个string引用,这个效率很高,在返回string后,如果企图在builder中修改内部的string类型则会自动重新分配一个char型数组,防止先前的那个string被影响;
两种情况重新分配内存:
创建的string大于容量 或者是 在返回string后企图再次修改字符串;
容量、最大容量、保证容量等,同string相同,它每次返回的都是引用所以可以连续调用操作函数;

ToString()详解:Object默认是为每一个对象返回自己的全类型名,即.GetType.ToString 
无参ToString有两个缺点,第一:不能对字符串进行控制,第二:不能根据特定文化进行控制格式化;
public interface IFormattable   {
                      String ToString(String format, IFormatProvider formatProvider);     }
第一个参数是格式化字符串,除了这个参数外,一般数值类型也支持图格式(writeline中的{}),如果这个参数为空,则会默认其为G(general最常用的格式),无参ToString就默认为G;
public interface IFormatProvider {
                    Object GetFormat(Type formatType);     }
第二个参数是一个实现了IFormatProvider接口的类型,如果为空则默认为于线程相关联的文化信息(同无参ToString)如果仅为了编程需要则没有必要实现这个接口,直接传递一个null;而cultureInfo就实现了这个接口,可以通过构造一个CultureInfo对象并传递给函数;也可以传递一个CultureInfo.InvariantCulture 表示没有文化侧重;
在FCL中只有三个类实现了这个接口CultureInfo, NumberFormatInfo ,DateTimeFormatInfo,后面两个只是简单的返回自己,没有实际意义;

在内部实现这个函数时:会通过接口调用文化信息的System.Globalization.NumberFormatInfo 及DateTimeFormatInfo
来调用它的属性选择使用内部的一些文化符号信息等;
NumberFormatInfo nfi = (NumberFormatInfo)formatProvider.GetFormat(typeof(NumberFormatInfo));

格式化多个对象到一个字符串:string.Format( )
"{n,m:x}" 含义是n 是参数索引,m是强制的宽度值(若为正则右对齐,若为负则左对齐);x是表示应如何格式化该项(如:C D E F G N P X),或者是图格式(自定义格式) 用这些参数来调用相应对象ToString方法,没有则传递一个null ;

定义自己的格式化:即在StringBuilder.AppendFormat 或者是String.Format 中有一个版本可以传递一个IFormatProvider ,你可以实现这个接口再实现下面的那个接口的类作为参数传递过去:
public interface ICustomFormatter {
String Format(String format, Object arg, IFormatProvider formatProvider);     }
这样将这个参数传递给上面的两个函数就可以在FCL框架内部检查是否实现了ICustomFormatter接口,如果实现则就执行它的Format函数,这样就实现了调用你定义的一个函数的目的。
注:可以在构造此类型的时候传递以个文化信息,以保证相应的吻合(也可以默认使用线程文化)如:
public Object GetFormat(Type formatType) {
                if (formatType == typeof(ICustomFormatter)) return this;
                return Thread.CurrentThread.CurrentCulture.GetFormat(formatType);//此处!
}
在定义CF接口函数的时候,要实现相应的文化信息(对象需要文化信息的时候)
IFormattable formattable = arg as IFormattable;
if (formattable == null) s = arg.ToString();
else s = formattable.ToString(format, formatProvider);

编码解码:encode/decode
在CLR中所有的char都是16位的Unicode,而string都是由16位的Unicode组成,由于直接存储或传递会导致内存大量浪费所以要先进行编码成为压缩的byte数组,有以下几种编码形式:
1.UTF-16   每一个16位的char转换为2个字节;
2.UTF-8   默认为BinaryWriter/StreamWriter/BinaryReader/StreamRead 的编码和解码, 每一个16位的char转换为1或者2,3甚至4个字节
3.UTF-32,    UTF-7,    ASCLL
使用:System.Text.Encoding 中获得相应编码的实例(提供默认的静态属性),还有一个静态函数GetEncoding 允许你来指定一个code-page(如:932),获得相应的实例;优点:第一次使用时时创建,第二次使用的时候就是直接返回以前创建的对象,这样可以提高性能;
UnicodeEncoding, UTF8Encoding, UTF32Encoding,  UTF7Encoding也可以自己构造一个编码实例,通过构造参数来加强编码控制,而AscIIEncoding则没有任何必要自己创建;
编码实例函数GetByteCount/GetCharCount  返回精确编解码大小,GetMaxByteCount/GetMaxCharCount 返回大致大小,可以提高速度;
为实现同步问题,可调用.GetEncoder 或者.GetDecoder ,来实现同步编码,解码;
安全字符串:System.Security.SecureString 对象来使用安全字符串;

枚举类型内部实现的是一个结构体:

 internal struct Color : System.Enum {

  public const Color White = (Color) 0;

  public const Color Red = (Color) 1;

  public Int32 value__;

 }

 


所以枚举类型确定于编译时,有着与const相同的缺点;
public static Type GetUnderlyingType(Type enumType) ;  获得支持此枚举的数据类型;
internal enum Color : byte {White,    Red}         FCL只支持 byte, sbyte, short, ushort, int, uint, long,  ulong;
转换为string:
ToString()/   public static String Format(Type enumType, Object value, String format) ;
public static Array GetValues(Type enumType);      
获得此枚举类型相对应的值数组;可以再强制转换成枚举类型就可以获得此类型所有的符号;
public static string GetName(Type enumType, Object value);
public static String[] GetNames(Type enumType);
转换为值:
public static Object Parse(Type enumType,  String value);
public static Object Parse(Type enumType,  String value,  Boolean ignoreCase);
第二个参数可以使符号字符串,也可以是值字符串;
public static Boolean IsDefined(Type enumType, Object value);                是否定义此符号或值;

如果将枚举类型作为标志位的话:加【Flags】属性,还要显示为每一个符号赋值:0x0001(32位数的16进制)
枚举类型的ToString()函数的执行过程:
1、检查此类型是否有【Flags】属性,如果没有则直接找于值相等的符号并返回,否则到二;
2、以定义顺序获得枚举类型所有值;
3、每个值于其做与运算,若值不变则符号加入输出字符串,直到检查完毕或者其值为0;

值类型数组和引用类型数组的区别:引用类型需要分配一次数组空间还要分配一次对象空间;
Array .Copy() 的几个作用:第一:将值类型装箱;第二:将装箱的引用类型拆箱;第三:向兼容类型的转换;
复制引用类型时是浅复制;
当你声明一个数组,CLR会自动生成一个继承与Array的类型,它继承Array的所有方法,都默认实现了三个接口:IEnumerable         ICollection           IList
由于多维数组及非零基数组的原因,CLR没有实现其对应的泛型接口,然而当一维零基数组创建的时候CLR会自动给创建的数组类型实现其泛型接口,还实现了其基类所有的泛型接口,值类型只实现自己的三个泛型接口;
Object
Array (non-generic IEnumerable, ICollection, IList                              )
Object[] (IEnumerable, ICollection, IList                               of Object)
String[] (IEnumerable, ICollection, IList                                of String)
Stream[] (IEnumerable, ICollection, IList                              of Stream)   为基类实现对应的泛型接口;
FileStream[] (IEnumerable, ICollection, IList                        of FileStream)
这样声明一个FileStream[]对象就可以于上面所有的接口相兼容;

方法返回数组时,如果数组中没有元素则可以返回null 或者是长度为零的数组,但为了防止不抛出异常及不必要的检查,建议使用长度为零的数组;
创建非零基数组:
Array.CreateInstance( type  ,  int[] ,int []) ,第二参数为各维的长度数组,第三参数为各维的基数;
返回一个Array类型要访问元素  如果是一维则不需要转换可以直接使用GetValue SetValue  来访问其中的元素,如果多维 则要转换为对应类型的数组才能访问。
如何得知数组的各维的下界及上界(下界+此维的长度):转换后的数组.GetLowerBound(维数0~n-1)

CLR中数组访问方式及其性能:
一维:零基数组:string[]           非零基数组 :string[*]
多维:均为string[ , ]           (CLR将所有的多维数组看作是非零基的,即应该表示为string[*,*])
一维零基数组的优势:有对应的il指令来直接操作数组,访问元素时不要减去维的基数,JIT会对其下标检查代码进行优化     (测量运行时间:stopwatch)
对于锯齿形数组不推荐使用,第一初始化麻烦且时间较长,第二提高不了多少性能;
对代码的权限修改:CASPol.exe ;

接口的继承:
类型继承接口后默认接口是 virtual  sealed  如果显示标记其为virtual时,则允许派生类覆写此函数(unsealed);
显示或隐式实现方法在调用上的不同的分辨:
当CLR加载一个类型的时候,它会构造一个方法表:自定义方法+继承方法(类继承方法+接口继承方法)
c#编译器会默认根据方法签名是否匹配(除virtual标识),如匹配则会指向同一段实现;当显示实现的时候,不允许加任何修饰符,因为编译器会默认添加其为private 使得对象实例不能直接访问这个方法;(只能通过接口类型变量来调用接口方法)
泛型接口的优点:
1.类型安全,在编译期就可以确定;
2.当以值类型为参数的时候可以减少装箱次数;
3.使用不同的类型参数的泛型,可以被一个类同时继承多次,达到函数重载的目的;
泛型接口中的类型参数约束:(泛型约束)
约束项:泛型约束可以减少值类型装箱:参数是值类型但是又需要某种接口支持的情形,其二 :在值类型调用对应接口的方法的时候,IL会自动不装箱来调用(只有约束可以使用这种指令)
1、:new()          类型参数必须要有无参构造函数,有多个约束时,必须要将此约束放到最后;
2、: struct        类型参数必须是值类型;
3、:class           类型参数必须是引用类型;      (2,3不可重复)
4、:基类名      类型参数必须是基类及其派生类;(不可既指定约束基类又指定3)
5、:接口名      类型参数必须是指定的接口或者是实现了该接口,可以定义多个接口约束同时约束接口也可以是泛型的;
6、:U              T提供的类型参数必须是U提供的类型参数或派生自U提供的类型参数(裸类型约束);

如果一个接口没有泛型版本,那么为了提高类型安全及减少装箱,可以有以下改进:
public Int32 CompareTo(SomeValueType other) {
 do something ;  return;         }
Int32 IComparable.CompareTo(Object other)     {
 return CompareTo((SomeValueType) other);       }

委托中的+=/-= 其实就是一个重载的函数:(delegatename)Delegate.Combine(original,  newadd);/Remove;
委托可以越过访问修饰符来任意调用回调函数;,委托链中可以有静态的函数也可以含实例函数;
委托的继承链: Object ——>Delegate ——>MulticastDelegate——>自定义委托;
因为某些原因Delegate中的一些Combine,Remove函数只接受Delegate型参数,导致M也要继承Delegate ,所有的委托类型都要继承自MulticastDelegate ,其内有一些较为重要的字段:
1、Object  _target  如果委托对象包装的是实例方法那么这个字段指向对应的实例,这个字段在调用实例方法时传递给其this参数,否则若为静态方法 其为null;
2、IntPtr  _methodPtr   一个CLR使用的内部整数来标识回调函数的地址;
3、Object _invocationList  当建立一个委托链的时候,它指向这个委托链数组;
internal class Feedback : System.MulticastDelegate {
 public Feedback(Object object, IntPtr method);      构造函数;(存于1、2中)
 public virtual void Invoke(Int32 value);                  与委托方法原型相同的方法;
 public virtual IAsyncResult BeginInvoke(Int32 value,AsyncCallback callback, Object object);
 public virtual void EndInvoke(IAsyncResult result);
}
Delegate有两个属性:Target、Method 分别返回1和2 两个字段(第二个字段返回MethodInfo)
用:委托对象(参数列表),来调用委托就相当于   委托对象. Invoke(参数列表);可以显示调用,默认编译器会自动生成相应的代码;
关于第三个字段的意义:当有一个委托链的时候,首先检查链头是否为空,如果为空则直接将待链委托赋给链头并返回(第三个字段为null);如果不为空的时候则重新分配一个委托对象,其1、2两字段不管,而第三个字段则指向一个委托类型数组,这些数组指向相对应的已经包装好了的委托,以后在添加都会按步重新执行;
移除委托的时候,从后往前检查委托数组所维护的委托对象是否有1、2字段都相同的委托项,如果找到并且委托数组中还剩下一个 那么委托数组返回,如果找到且还有多个委托对象则新建一个新的委托数组,并用旧的数组初始化新的数组。如果只有一项 ,那么返回null;所以即使有多个相同的委托匹配项,那么也只会移除一个项;
Invoke 算法的缺点:不能返回每一个委托方法的返回值,如果发生异常会阻断所有的方法调用;
解决方案:MuticastDelegate . GetInvocationList  () 返回一个Delegate[ ] 如果_invocationList 为空则 返回的数组只包含一项;
在多事件类型中,例如:Control 类可能触发多种事件,为了触发事件要根据事件的类型来选择对应的委托,但是并不知道调用哪个委托及需要哪些参数:
使用CreateDelegate   DynamicInvoke  来动态创建委托和调用委托,后着接收参数数组;

Generic Collection Class   Non-Generic Collection Class
List<T>      ArrayList
Dictionary<TKey, TValue>    Hashtable
SortedDictionary<TKey, TValue>   SortedList
Stack<T>      Stack
Queue<T>     Queue
LinkedList<T>     (none)
在CLR看来一个泛型类型,加载时也如同一个Type Object一样,但是在泛型的内部却不能向其他的类型一样来定义静态字段,但是可以用静态函数来得到和泛型约束相同的效果(泛型约束中不能限定类型参数必须为枚举类型)
static GenericTypeThatRequiresAnEnum() {                          解决方案;
 if (!typeof(T).IsEnum) {throw new ArgumentException("T must be an enumerated type");}          }
泛型继承规则的特殊性:
当你使用一个泛型类型并指定了它的类型参数那么你已经在CLR中创建了一个新的类型,且这个新类型直接继承于泛型类型所继承的类型;
使用using 语句来使得泛型表示方法可读性更强;
泛型的重载:不能根据类型参数和约束来重载泛型方法可以根据维数来确定重载,泛型方法和普通方法可以由交集 CLR可以以最匹配为原则来选择相应的方法;
泛型的覆写:覆写的时候要保证所有的约束及类型参数个数不变;(同实现泛型接口)

[assembly: SomeAttr] [module: SomeAttr] [type: SomeAttr][field: SomeAttr]  [method: SomeAttr]              [param: SomeAttr] 
<[typevar: SomeAttr] T> { // Applied to generic type variable     [property: SomeAttr]                            [event: SomeAttr]
[return: SomeAttr] // Applied to return value        [field: SomeAttr]
属性就是一个类型的实例,这个类型必须直接或间接继承自Attribute 
应该将属性看作一个属性,这个类应该为必须的参数提供一个构造函数,而为可选的只要有对应的属性就可以了;
如何从已有类型中查找是否实现了相应的属性:
1、Type . IsDefined( ) 看某类型有没有实现相应的类型;
2、在属性对象中查找是否标记了相应的属性,System.Attribute 的三个静态函数来查找
IsDefined,  较常用且不会构造相应的实例;
GetCustomAttributes,    GetCustomAttribute  
3、反射空间中定义了Assembly, Module, ParameterInfo, MemberInfo, Type, MethodInfo, ConstructorInfo, FieldInfo, EventInfo, PropertyInfo 所有的类型都提供了IsDefined 和 GetCustomAttributes
以上三种方法中,1、2及3中的MethodInfo  提供了是否有属性继承的标记;
当将属性类型作为参数传递的时候,方法仅查找其或其派生类属性(如果有派生的话返回后还需要验证)
 Type类型可以直接赋值给MemberInfo类型?
如果需要两个属性判等的操作的话则要覆写 Attribute的Match方法,基类默认其内部调用的是基类的Equals函数;
在不创建属性对象的情况下来查找并使用属性:
为防止构造函数中的非安全代码的执行,可以使用System.Reflection.CustomAttributeData  这个类提供了一个静态函数 GetCustomAttributes 这个方法有四个重载(Assembly Module  ParameterInfo  MemberInfo)这个方法可以返回针对特定对象的属性信息,此类的一些属性诸如:ConstructorArguments  NamedArguments  等属性可以访问到相应的信息;
条件编译属性:[Conditional( )]        ?

Nullable<T> 实际上是一个内部只维护了一个值类型和一个是否含有值的bool型参数的结构体,
Nullable<Int32> x = 5;   Nullable<Int32> y = null;                 ==                   Int32? x = 5;     Int32? y = null;
值类型可以隐式转换为可空类型,可空类型需要显示转换为值类型  单目操作符如果操作数位null ,则结果为null; 双目操作符如果有其一为null,则结果为空;==、!= 如果均为空或者值相等则相等;比较操作符 有一为null则为false ;
??操作符: x=a??b ;              ==            x=(a!=null)? a:b ;
可空类型的装箱并不是对Nullable<>类型直接装箱,而是根据是否为空来决定的,如果为null则直接返回null,如果不为null则返回值;拆箱时,如果不为空,则正常赋值,如果为空则赋null ;

在对异常调试运行的时候,可以再监视窗口中监视 $exception 变量来调试异常;
如果异常在finally块中抛出,那么CLR会认为这个异常是在finally块后面抛出的,这样就不能得到在try块中得到的异常信息,被后者覆盖了;(当异常需要到堆栈的下一层去查找对应的catch块时,会发生这种情况)
CLS /Non-CLS  Exception :c# 只允许抛出前者,但是为处理与其他允许抛出后者的交互问题就创建了System.Runtime.CompilerServices 中的RuntimeWrappedException 类来包转所有的非CLS异常至CLS异常,其有一属性WrappedException 包含了真实抛出的非CLS异常对象;
异常是方法对程序隐式假设的一种违反,不是错误;例如:在读取文件时,找不到文件,这个是一种非可控力能够改变的东西;
Message                 对应异常相应的信息,可以写入日志;
Data                      指向一个key/value的集合
Source              抛出异常的程序集名称
StackTrace               包含抛出异常的名称及签名
TargetSite                包含了抛出异常的方法
HelpLink                 ....
InnerException      通常为null,如果这个异常在处理另外一个异常时抛出则不为null;
自定义异常类必须继承自Exception 且要实现四个对应的基本构造函数,还要为自己的字段实现新的构造函数;异常类必须要是可序列化的,1:[serializable]           2:实现ISerializable接口;?如何实现
定义方法的时候首先要使你的所有参数合法,如果不合法直接抛出异常!
当方法接收一个引用类型的时候,如果为防止在调用过程中被其他线程改变引起参数不合法,导致异常则要复制引用类型在返回;(保证参数处于不变状态)
通常捕获异常后又重新抛出是为了能够维持接口的稳定,集函数a中调用函数b,要使得b抛出的异常符合a的异常标准;并将其作为内部异常;

资源清理:Finalize  方法,在c#中必须要用c++语法来声明,但是不具有c++中显示调用的性质;
~typename()                     ==                          Finalize()
{  block;    { try{block;}finally{base.Finalize()}
}                                                                     }

System.Runtime.ConstrainedExecution 中的CriticalFinalizerObject 类CLR赋予其及其派生类三个特殊的属性:
1、当对象实例创建的时候CLR会将其在继承层次中所有的Finalize方法全部编译,主要是防止要编译时发生一些诸如:内存不足,程序集无法加载等情况时发生的此方法无法调用所引起的资源泄漏;
2、CLR在调用非此类或其继承类Finalize 方法之后才调用此类或其继承类的Finalize方法,这就保证了在前者中可以在自己的Finalize方法中访问到后者的对象实例;
3、发生AppDomain强制中断,CLR也会调用其Finalize 方法;

System.Runtime.InteropServices中的SafeHandle抽象类,继承于CriticalFianlizerObject类且实现了IDisposable接口,其中有抽象方法ReleaseHandle和抽象属性IsInvalid{get};
Microsoft.Win32.SafeHandles中的SafeHandleZeroOrMinusOneIsInvalid 抽象类继承于SafeHandle抽象类,其覆写了IsInvalid属性,当safeHandle中的IntPtr值为0或-1时,则返回true,说明是非法的资源;此类型亦是抽象类所以需要有继承类来覆写ReleaseHandle函数;
Microsoft.Win32.SafeHandles中的两个类SafeFileHandle和SafeWaitHandle派生于其,覆写方法实现为{return Win32Native.CloseHandle(base.handle);} 两者的唯一区别为:后者可以作为参数传递给方法,而前者没有类型安全所以不可以传递给其;

这两个类型可以用来与非托管代码进行资源参数的传递,以保证资源的及时释放;一般不会用在托管代码中;

引起Finalize 方法调用的原因由:
1、第0代已满;2、显示调用GC.Collect(int)方法;3、windows报告内存不足条件;4、CLR卸载一个AppDomain ;4、进程关闭,卸载CLR;
Finalize方法调用的时机及过程:
在托管堆中有这样几类对象 a.有根  b.有Finalize 方法;(有四种组合类型)
对于有Finalize方法的(除继承自Object的),在Finalization list 中均包含了一个指向托管堆对应对象的引用;
当发生第一次垃圾回收时,如果不可达对象能在Finalization list 中找到它的引用,那么这个引用从Finalization list 中移至freachable(有F方法且可达) queue (这个里面所引用的对象都是有自己的Finalize方法要调用);找不到引用(无Finalize方法的)则被回收;   第二次垃圾回收的时候,被拥有特殊线程调用的freachable list 为空,上一次所有无根不可达对象均已在代龄2;GC只回收不可达对象;

为了能够显示的回收资源,(不可能等到垃圾回收的时候才释放资源),所以要实现 dispose 模式;public interface IDisposable {         void Dispose();  } 一般在实现Dispose方法的同时还实现了close方法;
此处有几点值得注意:
1、实现Finalize方法与IDisposable接口,它们共同调用一个protected virtual void Dispose(bool);这个类型的设计如下:
IsDisposed=false;
public void Dispose() {      Dispose(true);       }          
public void Close()    {       Dispose(true);       }
~SafeHandle()           {       Dispose(false);      }
protected virtual void Dispose(Boolean disposing)
 {  
if(!IsDisposed){
if (disposing) {用自己的逻辑来显示释放对象中引用的各个对象,它们肯定没有释放或调用一些函数处理善后                            工作      } 
本地非托管资源的释放    
GC.SuppressFinalize(this)     //GC不会将这个引用从finalization list 到freatchable list 而是直接释放 
} IsDisposed=true;                //多次调用Dispose则不执行任何东西而直接返回
}
在继承层次中可以直接覆写其保护的虚Dispose方法(其内应该调用基类的Dispose方法),派生类要释放的时候只要调用继承于基类的公有Dispose方法即可完成整个层次的资源释放;
定义你自己的类型时,如果对象已经显示的Dispose了那么每一个方法都要能够抛出ObjectDisposedException 异常,如果多次调用Dispose则return;

控制对象的生存周期:
CLR为每一个AppDomain都提供了一个GC handle table ,这个表允许程序来控制对象的生命周期,每一项都包含了一个指向托管堆内存对象的指针和一个标记,可以通过GCHandle来增加或移除表项
GCHandleType:(runtime.intopservice)
1、Weak:可以获知对象目前是否可达,但不可获知Finalize方法是否执行(不知道是否还在内存中);
2、WeakTrackResurrection:      weak的第二个也可以获知;
3、Normal  :函数Alloc的默认选项,即使此对象不是根但是也留在内存当中,当GC收缩内存的时候,位置可能会被移动;
4、Pinned:Normal 的第二条,它在托管堆中不可以被移动(传递给非托管代码);
调用Alloc来增加表项的时候, CLR会找一个表项来存放你传递对象的地址及相应的标记,返回一个GCHandle的对象(是一个包含了IntPtr (即表索引)的值类型),不需要时可以调用.Free()来释放此表项;

在GC时,如何处理这个特殊表项:
1、在查找过所有可达对象后,GC遍历此表,所有3、4标记的均被看做是根(可达),包括其内引用的对象;
2、查找所有含1标记的表项,如果它所指向的对象不可达,那么这个指针置null;
//3、查找finalization list ,如果其中有是不可达对象那么移至freachable queue ,并标记为可达;
4、查找2标记的项,如果它所指的在freachable list中的项不可达时,置Null;
5、当压缩托管堆内存时,有4标记的表项不会被移动;

1、使用Normal标记:495?在非托管代码中回调托管代码(即在非托管低码中作为一个参数过渡引用)
2、使用Pinned标记:将托管对象传递给非托管代码来操作;
CLR's P/Invoke  :实际上就是自动pin 参数,非托管代码返回时unpin 参数;但是非托管代码返回但仍需要托管对象指针;
3、使用Weak :应用场景为,你不想因为有一个对象的引用而阻止了这个对象的回收;
例如:a b 两类,在生成b对象时,用a的某实例方法向b注册了委托,那么b可以调用GCHandle.Alloc 传递给其一个委托对象和Weak标记,然后b将返回的GCHandle实例存于GCHandle数组中,在调用委托的时候在强制.target检验其不为零后转化为委托,再触发委托;(若为空,则及时释放GCHandle实例)    弱引用;
4、2于1 的唯一区别就是2可以确定对象是否已经被回收了;一般不用2标记;

GCHandle 被System中的WeakReference 类包装,它构造函数调用它的Alloc,Finalize方法调用Free,Target属性调用Target,且它只支持GCHandle的Weak标记;(缺点是其没有实现Dispose,不能显示释放GCHandle实例)用法与GCHandle对象相同;

手动给GC增加虚压力;(资源句柄并没有实际加载到内存中,当进行操作时需要分配内存)
public static void AddMemoryPressure(Int64 bytesAllocated);
public static void RemoveMemoryPressure(Int64 bytesAllocated);
本地资源数目的限制:System.Runtime.InteropServices  HandleCollector 为防止超过操作系统可匹配的最大资源数,而设立的可接受固定阀值的类,来控制资源数目,超过即会自动释放,也可以手动释放或添加;其:
Add /Remove 函数放在构造和拆构函数中去,来限定类的数目;

public sealed class MemoryFailPoint : CriticalFinalizerObject, IDisposable {
 public MemoryFailPoint(Int32 sizeInMegabytes);
 ~MemoryFailPoint();
 public void Dispose();    }
这个函数用于预测对于耗内存较大的算法是否能够成功执行;

手动控制垃圾回收器,何时调用垃圾回收:
void GC.Collect(Int32 Generation)
void GC.Collect()
GC.WaitForPendingFinalizers(); 挂起当前线程直至垃圾回收结束;
Int32 GetGeneration(Object obj)
Int32 GetGeneration(WeakReference wr)        获得当前对象引用或者弱引用所在的代龄;

LOH 用于分配对象大于85000字节的大对象,其被GC看作是代龄为2的堆;
垃圾回收检测:gc内存使用情况
Int64 GetTotalMemory(Boolean forceFullCollection);       
Int32 CollectionCount(Int32 generation);
CLR profiler 及 Perfmon.exe 两个用来测试 CLR内存回收效率的工具;

加载并控制CLR:
微软是将CLR作为DLL里面包含的一个COM服务器来实现的,CLR定义有专门的COM接口,并为这个接口和COM服务器分配了GUIDs,当你安装.NET的时候,这个代表CLR的COM服务器在注册表中注册;
任何应用程序可以加载CLR (不需要调用CoCreateInstance来创建一个COM服务器的实例);
非托管代码可以调用CorBindToRuntimeEx 函数(在MSCorEE.h中声明),这个函数在MSCorEE.dll中实现,这个文件是用来决定加载哪个版本的CLR,它并不包含任何CLR内的实现信息;一个机器可以安装了多个版本的CLR但是,这个文件的版本只有一个(除了64位的),且是最新的,所以其知道所有的CLR版本信息;CLR实现在MSCorEks.dll文件中;这个方法使用特定的版本信息在加上自己收集的信息来确定加载CLR的版本;
托管代码加载时,MSCorEE.dll会检查文件的CLR头来决定加载的CLR版本,这个可以通过配置文件中的requiredRuntime 和supportedRuntime覆盖这个信息;
CorBindToRuntimeEx函数返回一个指向非托管的ICLRRuntimeHost 接口,其定义了如下方法:
1、Set Host managers  2、Get CLR managers 设置或获得对CLR的配置;
3、初始化,启动CLR;加载程序集;停止CLR 使得托管代码不在运行;
在非托管代码中CLR只能加载一个版本且一次,直到进程结束才能卸载CLR;

使用托管代码来管理CLR:使用AppDomainManager 的继承类来覆写CLR的默认行为,你需要的是定义自己的派生类然后覆写其设置方法,然后安装到GAC ,每一个进程只能有一个此类或其派生类与其相联系,然后所有的程序域都使用同一个配置,如何将进程于这个控制类相联系:
1、使用非托管代码CorBindToRuntimeEx,再查询ICLRControl 接口调用其SetAppDomainManagerType函数,并将程序集的标记和派生类的名称传递给其,来完成绑定;
2、环境变量,Environment.SetEnvironmentVariable来设置:APPDOMAIN_MANAGER_ASM为程序集标记,APPDOMAIN_MANAGER_TYPE为派生类名称;
3、注册表;

AppDomain:第一个CLR加载的时候创建的为默认的应用程序域,这个域只有在进程终止时才销毁;除了默认的AppDomain,用户可以使用托管代码或非托管代码来创建新的AppDomain;
应用程序域的特性:(最终的意图为提供代码隔离)
1、在一个程序域创建的对象不能被另一个程序域直接访问;
2、程序域可以被卸载(其中所有程序集被卸载);不提供在程序域中卸载一个程序集
3、程序域可以独自指定自己的安全级别;
4、程序域可以被单独配置;

程序域于程序域之间的关系:
CLR堆的种类:线程堆 +LOH 堆+GC堆(实例堆+加载堆—类型堆);
两个程序域之间隔离的程度是:自己加载自己要使用程序集而互不影响有独立的CLR堆,如果使用相同的类型那么在各自的加载堆上必须自己加载对应的类型;但是为节省通用类型的加载空间,所以设计了
AppDomain-neutral 这里面的所有的东西都在所有的程序域中共享,但是代价是一旦加载的程序集就不能被卸载,只有在进程结束的时候才能卸载这个程序域; 

线程,进程和程序域的关系:
多个程序域可以共同存在于一个进程当中,一个线程可以在一个程序域中执行代码,再到另外一个程序域中执行另外一个代码,在任何时刻线程都是工作在任意的一个程序域当中,可以调用Thread. GetDomain()或者是
AppDomain的CurrentDomain属性;
创建:AppDomain.CreateDomain() 需要指定相应的名称、安全性、配置。
加载程序集:AppDomain的实例函数 CreateInstanceAndUnwrap返回一个对应程序集中的实例对象;
卸载程序域:Unload()(卸载了其内的所有程序集)
Environment.Exit(0);            结束进程

跨程序域互操作:先检查是否符合一,再检查是否符合二;若均不符合则抛出SerializationException;
使用CreateInstanceAndUnwrap()返回另外一个程序域的引用,这样就破坏了程序域之间的独立,解决方案如下
1、引用:
此类型继承于MarshalByRefObject 这就代表CLR会采用引用的方式来跨过边界,当将这个实例返回到另外一个程序域的时候,CLR会在目标程序域的LH上定义一个代理(使用源类型的元数据)初始化其字段为指向源程序域和真实对象的指针,返回代理的实例;如果你调用GetType,CLR会欺骗你其为原类型System.Runtime.Remoting.RemotingService的静态函数 IsTransparentProxy可以分辨是否是代理,当使用代理类型来调用其方法的时候,线程会从这个程序域跳转到新的程序域,线程使用代理实例的GCHandle字段在原程序域中找到真正的对象再调用实际的函数(在Debug中可以发现线程的跳转),当原程序域卸载后,在使用代理对象调用相应的方法会抛出AppDomainUnloadedException异常;
内部实现机制:一个继承与MarshalByRefObject的类型可以定义实例字段,但是这些实例字段并不作为代理类型的字段,JIT使用代理对象发现目标程序域和真实对象时,调用System.Object的 FieldGetter和 FieldSetter使用反射机制来获得和设置相应的字段——所以效率很低;任何使用这种方法来跨边界的避免使用任何静态成员;
2、传值:
此类型要有可序列化标记[Serializable] ,首先在源程序域中构造好实例并序列化至Byte[],这个数组从源程序域复制到目标程序域,CLR反序列化数组到目标程序域(这就意味着CLR加载了被反序列化至目标程序域的类型所在的程序集—CLR会使用目标程序域的配置来加载新的程序集),这时候CLR创建了类型的实例并用数组进行初始化,这时候返回对应的对象就实现了跨程序域;

程序域之间的传递参数机制:当传递的时候,如果参数继承于MarshalByRefObject 就按引用传递跨边界,不然检查是否有[Serializable]标记,如有则按值跨边界,如果两者都不符合则抛出异常。返回值的机制于其相同
 string类型,不符合一但是符合二,但对于string   CLR有特殊机制 因为string为不变类型所以直接传递string类型的引用;(原则上所有类型都可以使用值类型来传递,因为大多数都有可序列化标记)

CLR内部加载程序集也是调用 System.Reflection.Assembly .Load() 函数,加载成功则返回Assembly 加载失败则抛出System.IO.FileNotFoundException 异常;
两种加载方式:
AppDomain 实例方法 Load   允许你加载一个程序集至特定的AppDomain ,CLR会将程序域特定的策略去加载程序集,然后返回一个Assembly的实例,但是其并不是派生至MarshalByRefObject,所以必须以值类型来跨程序域边界,然后又会用自己程序域的策略来自己加载程序集;
public static Assembly LoadFrom(String path);
Assembly.LoadFrom(@"http://Wintellect.com/SomeAssembly.dll");
加载固定路径的程序集其内部调用:AssemblyName.GetAssemblyName()来查找文件及元数据,最后返回
AssemblyName对象,再调用Assembly.Load () 加载程序集

public static Assembly ReflectionOnlyLoadFrom(String assemblyFile);
public static Assembly ReflectionOnlyLoad(String assemblyString);
只加载程序集但是不执行任何代码,一旦执行抛出InvalidOperationException

反射的性能:
1、在编译期没有类型安全检查,反射基本上均使用string ,所以无法检查类型安全;
2、速度慢,运行期检查究意味着在反射时期要遍历程序集的元数据查找类型的匹配项;
3、使用反射调用方法,需要先将参数打包进Object数组,再在线程堆栈中解包.....
使用:
1、在程序集中使用反射发现类型:(Type[] a.GetExportedTypes()  a为加载的程序集实例;)
2、

Type Object:
System.Type 继承于System.Reflection.MemberInfo ,System.RuntimeType, System.ReflectionOnlyType,  System.Reflection.TypeDelegator 及System.Reflection.Emit命名空间中的 (.EnumBuilder , .GenericTypeParameterBuilder, .TypeBuilder) 均继承与Type类型;
Object.GetType 返回的就是一个RuntimeType,在AppDomain中每一个类型仅有一个RuntimeType于其相对应;
1、System.Type 类提供了GetType的几个静态函数,所有的函数均只含一个string参数,类型要制定命名空间,也可以指定程序集(默认为当前及MscorLib.dll);
2、System.Type 的静态函数ReflectionOnlyGetType,返回的Type只能加载但是不能执行;
3、System.Reflection.Assembly 的实例方法GetType GetTypes GetExportedTypes 
4、System.Reflection.Module 的实例方法 GetType  GetTypes  FindTypes

从Type 获得类型实例:
若是跨程序集创建则会默认返回一个ObjectHandle类型,在调用Unwrap 加载对应程序集,如果是按值跨界则反序列化,如果按引用跨界则创建代理类及对象;
System.Activator .CreateInstance 
System.Activator.CreateInstanceFrom
AppDomain实例方法:CreateInstance, CreateInstanceAndUnwrap, CreateInstanceFrom, CreateInstanceFromAndUnwrap
System.Type实例方法InvokeMember
System.Reflection, ConstructorInfo's Invoke
如果你想创建一个没有构造函数的值类型只有调用第一个函数;上面的方法可以构造除数组和委托的所有类型,若要创建数组则调用Array的静态CreateInstance方法,若要创建委托则调用Delegate的静态CreateDelegate方法,若要创建泛型类型则遵循以下步骤:
Type openType = typeof(Dictionary<,>);
Type closedType = openType.MakeGenericType(new Type[] { typeof (String) , typeof(Int32) });
Object o = Activator.CreateInstance(closedType);
  
反射获得类型的继承体系:
System.Object
 System.Reflection.MemberInfo    GetMembers
  System.Type                         //类型可以作为其他类型的成员GetNestedTypes
  System.Reflection.FieldInfo   GetFields
  System.Reflection.Methodbase
   System.Reflection.ContructorInfo  GetConstructors
   System.Reflection.MethodInfo  GetMethods
  System.Reflection.PropertyInfo    GetProperties
  System.Reflection.EventInfo   GetEvents

另外的函数:GetMember, GetNestedType, GetField, GetConstructor, GetMethod,GetProperty, GetEvent根据特定的字符串来查找成员;MemberInfo的属性Name   MemberType   DeclaringType   ReflectedType   Module
MetadataToken   GetCustomAttributes   IsDefined

BindingFlags:查找成员的过滤器;
默认的返回标记为:BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static
public enum BindingFlags
{  CreateInstance = 0x200,    DeclaredOnly = 2,    Default = 0,    ExactBinding = 0x10000,    FlattenHierarchy = 0x40,
    GetField = 0x400,   GetProperty = 0x1000,    IgnoreCase = 1, IgnoreReturn = 0x1000000, Instance = 4,
    InvokeMethod = 0x100,   NonPublic = 0x20,   OptionalParamBinding = 0x40000,             Public = 0x10,
    PutDispProperty = 0x4000,PutRefDispProperty = 0x8000,SetField = 0x800,SetProperty = 0x2000, Static = 8,
    SuppressChangeType = 0x20000                      }


查找Type的接口:FindInterfaces, GetInterface,  GetInterfaces 获得接口;
若要指定获得MethodInfo所实现的接口,可以调用Type实例方法GetInterfaceMap,返回一个System.Reflection.InterfaceMapping 并根据其属性来检查相应的信息,其有四个属性:
1、TargetType  返回调用这个方法的Type;2、InterfaceType 返回调用参数的类型;
3、InterfaceMethods 返回此接口定义的所有方法数组;
4、返回类型对应于实现参数接口的方法数组;     3、4两者之间的参数数组相互匹配;

调用(访问)类型的成员:
调用方法:Type有一个实例方法
InvokeMember (
String name,   // Name of member
BindingFlags invokeAttr, // How to look up members
Binder binder,   // How to match members and arguments
Object target,   // Object to invoke member on
Object[] args,   // Arguments to pass to method
CultureInfo culture);   // Culture used by some binders
);
此方法调用情形:其会在类型的Members中查找匹配项,如果找到则调用(无返回值则返回null),如果没找到则抛出TargetInvocationException 其InnerException包含了真实的异常System.MissingMethodException, System.MissingFieldException,  System.MissingMemberException
找到相应的成员进行绑定,成员的名称可能出现重复(重载或函数于字段名称相同等),所有的参数除了target都是帮组函数唯一的确定来绑定:
binder参数:继承与System.Reflection.Binder 定义了绑定的规则定义了抽象的虚函数BindToField, BindToMethod, ChangeType, ReorderArgumentArray, SelectMethod, SelectProperty;
当你传递一个null到Binder参数时,函数会默认使用DefaultBinder 对象(FCL内部的),Type类提供了一个静态属性DefaultBinder,能够在使用的时候获得,binder需要以name BindingFlags 和调用需要的参数来作为其内部调用的参数,Default   IgnoreCase   Instance Static     Public     Nonpublic   FlattenHierarchy  返回基类的静态成员   DeclaredOnly 不返回继承成员    这些是搜索过滤标识,告诉binder搜索的范围,除了这些标记binder还检查参数类表(数目,类型),来寻得匹配标记ExactBinding 指精确查找参数匹配数目类型,标记OptionalParamBinding 指参数具有默认值及可变参数的参数匹配;
target参数:这个参数是调用这个方法的一个引用变量,如果是静态成员那么就传递一个null;

BinderFlags的调用标记:
InvokeMethod                                这些标记只可以有一个,这样就划定了类型的范围
CreateInstance    但是可以同时指定GetField 和GetProperty(同set)因为方法可以先匹配字段,
GetField     如果没有字段那么则可以匹配属性(防止无隐藏字段的属性)
SetField
GetProperty
SetProperty

由于Type的InvokeMember方法可以访问到所有的类型成员(除了事件),但是每一次调用都需要重新绑定,所以有了一次绑定,多次调用的机制:Type的Get...方法来返回固定类型的成员Info,如下:
1、FieldInfo   GetValue/SetValue     
2、ConstructorInfo  Invoke
3、MethodInfo  Ivoke
4、PropertyInfo  GetValue/SetValue
5、EventInfo   AddEventHandle/RemoveEventHandle
当按引用传递一个参数的时候,其类型为Type.GetType("System.int32&");

如果程序需要绑定很多Type、...Info 等则会占用大量内存,FCL定义了三个运行时的句柄:RuntimeTypeHandle, RuntimeFieldHandle, RuntimeMethodHandle 三个类型内部只是包含了IntPtr 均是值类型,其指向类型,字段方法在加载堆中的位置:
Type —>RuntimeTypeHandle       Type.GetTypeHandle/Type.GetTypeFromHandle
FieldInfo—>RuntimeFieldHandle   FieldInfo的实例属性FieldHandle/FieldInfo.GetFieldFromHandle
MethodInfo—>RuntimeMethodHandle        MethodInfo的实例属性MethodHandle/MethodInfo.GetMethodFromHandle

线程池:一个进程对应一个线程池,这个线程池供进程中的所有程序域共享,线程池支持异步操作即两个线程没有协作(同步),只管执行自己的回调方法;
获得或设置线程池的线程数目:
void GetMaxThreads(out Int32 workerThreads, out Int32 completionPortThreads);
Boolean SetMaxThreads(Int32 workerThreads, Int32 completionPortThreads);
void GetMinThreads(out Int32 workerThreads, out Int32 completionPortThreads);
Boolean SetMinThreads(Int32 workerThreads, Int32 completionPortThreads);
void GetAvailableThreads(out Int32 workerThreads, out Int32 completionPortThreads);

static Boolean QueueUserWorkItem(WaitCallback callBack);
static Boolean QueueUserWorkItem(WaitCallback callBack, Object state);
static Boolean UnsafeQueueUserWorkItem(WaitCallback callBack, Object state);
WaitCallback委托:   delegate void WaitCallback(Object state);
Object state是传递给委托方法的一个参数(可以为null);
当访问有限组员的时候,CLR提供Code Access Security(CAS)检查,CLR会检查线程中执行代码所在的程序集是否有允许访问资源,如果没有则抛出SecurityException

Timer 类:
1、System.Threading.Timer  在内部CLR只有一个线程用于维护所有的Timer对象,这个线程知道下一个Timer什么时候执行,当要执行的时候它就会内部调用线程池的QueueUserWorkItem来调用相应的回调方法,如果执行不过来则会多线程来执行(要保持线程同步);
2、System.Windows.Forms.Timer 
3、System.Timers

APM(异步编程模型):凡是实现带有Begin-.../End-...的函数的类均支持异步操作模型;(stream/Delegate等)
IO的异步操作:
所谓异步就是不让线程在进行内存加载等操作的时候在旁监视,而可以去做自己的事情,此为异步;
IAsyncResult BeginRead(Byte[] array, Int32 offset, Int32 numBytes,   AsyncCallback userCallback, Object stateObject)
一般采用构造异步FileStream再调用异步函数,来支持异步操作,也可以构造同步FileStream再调用同步函数(其异步函数亦是隐藏为线程等待),来支持同步操作(最好不要交叉使用);
模式一:Wait—Until—Done  
在调用Begin后执行其他代码,后再人为的调用End函数;
模式二:轮询
在调用Begin后,不断询问返回类型的IAsyncResult的一个IsCompleted属性,直至完成操作;
模式三:自动回调
在调用Begin的时候传递给一个方法委托,在方法委托中调用显示调用End函数,并可以用参数的AsyncState属性来获得第四个参数,这时候操作已经完成(但是End返回的值不一定是result),其后可以执行相应的对结果的操作;public delegate void AsyncCallback(IAsyncResult ar);
注:如果要完成多IO操作则可以定义不同的FileStream来调用Begin函数,并注册相同的回调函数(意味着两个文件采用相同的操作);异步操作不会阻止主程序的返回,所以要确定线程池仍然存在;

非IO(需要线程参加)的异步:
委托类内部默认生成:一个构造函数,一个调用函数,两个异步调用函数,其使用方法同上面的Begin/End函数调用方法;其实是注册到线程池中来在后台调用;

APM及异常处理:任何导致异步函数操作失败的异常都会在End函数中抛出,必须要在End函数上来捕获异常,否则会导致程序异常终止;

异步的取消:一般只要检查IAsyncResult 的接口中是否实现了关于Cancel的类型,那么就可以取消,否则不可以
System.Windows.Forms.Control 提供了三个参数Invoke, BeginInvoke, and EndInvoke;

Synchronization 线程同步:在异步中如果各线程访问到公共的数据,而不实现线程同步的话就会破坏数据的安全和完整性;
internal sealed class VolatileMethod {
 private Byte m_initialized = 0;
 private Int32 m_value = 0;
 public void Thread1()  {
  m_value = 5;
  Thread.VolatileWrite(ref m_initialized, 1);}
 public void Thread2() {
  if (Thread.VolatileRead(ref m_initialized) == 1) {
   Console.WriteLine(m_value) ; }
   }}
为了防止CPU缓冲区造成的刷新延迟,而导致锁(m_initialized)不能及时的更新而引起的访问不能同步;
VolatileRead —直接读取内存所指示的值而不管缓冲区;——Thread的静态函数;
VolatileWrite—刷新缓冲区,改变指定地址的值;
MemoryBarrier—刷新缓冲区;

关键字:volatile 指示在任何时候,这个值都是最新的值(隐式调用上面的函数),所有的访问都是直接访问内存,可应用于:仅可用于类或结构字段,不能将局部变量声明为volatile 
不支持将字段引用传递到方法;(不推荐使用,推荐使用Interlocked)
System.Thread.Interlocked 类的静态函数可以替代以上的Volatile的使用;

同步进程块:
在c++中,为了保持线程同步会为每一个类设置一个
class SomeType {
private: CRITICAL_SECTION m_csObject;             //为每一个此对象保存一个独立结构类型
public: SomeType()   {InitializeCriticalSection(&m_csObject) ;}  //初始化
 ~SomeType() {DeleteCriticalSection(&m_csObject);}     //释放
 void SomeMethod() {
  EnterCriticalSection(&m_csObject);       锁定
  // Execute thread-safe code here...
  LeaveCriticalSection(&m_csObject);}     解锁
 };  
在CLR中实际上也为每一个对象提供了一个类似CRITICAL_SECTION 的字段,CLR会负责其初始化及释放,但是为了对其内存进行优化采用了如下方法:当CLR初始化的时候,它分配了一个同步块数组,每一个同步块包含了于Win32中CRITICAL_SECTION相同的结构,分配对象的时候同步进程块索引(数组的下标)初始化为-1(不指向任何同步块),当方法调用来进入同步块时,CLR从数组中找到空闲同步块并分配其下标给对象的索引,当所有线程释放同步块时,其索引重新赋为-1,而其所指向的同步块可以另做它用;

使用Monitor类来操作对象的同步块: System.Threading.Monitor
static void Enter (Object obj);
首先检查当前对象的同步块索引是否有效(如无效则分配),Monitor.Enter检查特定的同步块是否正被其它线程占有,如果正被占有那么这个线程会被中止,直至占有的线程释放 ; 如果没被占有那么调用线程占有同步块
static Boolean TryEnter(Object obj);
static Boolean TryEnter(Object obj, Int32 millisecondsTimeout);
static Boolean TryEnter(Object obj, TimeSpan timeout);
尝试检查当前同步块是否可用/固定时间内是否可用;
一旦确定占有的空闲的同步块,则一定要匹配调用Monitor.Exit 
微软推荐使用Monitor.Enter(this)/Monitor.Exit(this),锁定实例方法操作;
c#为Monitor提供了简单的语法:lock
lock(this){}
 ==Monitor.Enter(this); try { ...} finally{ Monitor.Exit(this);  }

锁定静态方法的操作:
Type Object如同任何对象一样也有两个管理字段(其中一个指向Type,一个指向同步块),因为静态字段又是属于类型级的,这就意味着可以用Type Object的同步块来锁定对象静态方法的访问,如:
lock(typeof(classname)){};

弊端:在c++中,你不可能将CRITICAL_SECTION(临界区)设为 public!如果使用public的话,这样会导致恶意代码使用死锁所有使用这个实例的线程,CLR中与堆中对象联系的同步块如同是public型,任何能访问到对象引用的代码都可以将其传递给Monitor.Enter/Exit 将其上锁,甚至可以将代表Type Object的Type类型传递过去以锁住这个类型,还有基于共享型string类型,及跨程序集MarshalByRefObject 等类型均会意外出错;
如:如果在Main函数中给某对象加锁,那么在任何加锁的地方,如自己定义的函数,都会死锁;前提要是执行的线程不是同一个,不然两次加锁等于加一次锁;

System.Runtime.CompilerServices 命名空间中有一个属性MethodImplAttribute(MethodImplOptions.Synchronized)来标记一个方法,如果其为静态方法那么lock(Typename){...};如果是实例方法则lock(this){....};
所以这就导致了方法的局限性;

推荐使用加锁方法:
private  Object a= new Object();    //不可以使用值类型加锁,因为其每次都要装箱,返回不同的引用类型
lock(a){}                                         如果要使静态方法同步那么就要将a声明为静态类型;
原理:改为查找a中的同步块,且实现的同步块的私有化;

双重加锁技术:懒惰式创建型单例模式(需要时创建)
public sealed class Singleton {
private static Object s_lock = new Object();
private static volatile Singleton s_value;
private Singleton() { }
public static Singleton Value {
get {
if (s_value == null) {                              //为了性能上的考虑不要每次都进临界区
 lock (s_lock) {           //这个节点可能有多个线程同时访问到
if (s_value == null) {                             //防止创建了但是没有刷新至内存
s_value = new Singleton();}}}
return s_value;
}}}
代替版本:
public sealed class Singleton {
private static Singleton s_value = new Singleton();
private Singleton() { }
public static Singleton Value {
get {
return s_value; 
}}}
ReaderWriterLock类,定义支持单个写线程,多个读线程的同步资源访问锁;
WaitHandle                                内核对象继承层次如下:
 Mutex
 Semaphore
 EventWaitHandle
  AutoResetEvent
  ManualResetEvent
WaitHandle有一个维护Win32内核对象句柄的字段
.Close()关闭内核对象,内部调用CloseHandle;
.WaitOne()  等待其内核对象有信号,返回TRUE,或者在一定时间后返回false;
.WaitAny()   让调用线程等待在WaitHandle[]中的任意一个内核对象有信号,返回索引;
.WaitAll()     ............................................................................所有对象都有信号;
.SignalAndWait 

向线程池注册,当指定内核对象有信号的时候就执行(可多次执行)
public static RegisterWaitHandle RegisterWaitForSingleObject(
WaitHandle waitObject, WaitOrTimerCallback callback, Object state,
Int32 millisecondsTimeoutInterval, Boolean executeOnlyOnce);
public Boolean Unregister(WaitHandle waitObject);

可以同时编译几个CS文件到同一个程序集中去,就相当于几个类定义于同一个cs中;

你可能感兴趣的:(笔记)