• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

CLRViac#笔记

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

第一步用对应语言的编译器(针对运行时)生成相同的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     


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
c#模拟表单提交,postform上传PDF文件发布时间:2022-07-18
下一篇:
c++编译常见错误原因集中发布时间:2022-07-18
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap