第一部分:C#与.NET平台
源码-IL-汇编过程
.NET语言的编译分为两个阶段.首先高级语言被编译成一种称作IL的中间语言,与高级语言相比,IL更像是机器语言,然而,IL却包含一些抽象概念(比如:类、异常),这也是这种语言被称为中间语言的原因。IL被打包在DLL或EXE文件中,而DLL和EXE在.NET中的主要区别就是:只有EXE可以直接被运行,而二者都可被某个正在执行的进程动态装载。由于机器的CPU只能执行本地汇编语言,而不是IL,进一步将IL编译成汇编语言的工作(也就是第二阶段)需要在运行时进行,这个过程由即时编译器(JIT)完成。
高级语言在初次被编译时,编译器做两件事:首先把编译得到的IL存储在DLL或EXE中,然后为类的每个方法创建一个stub函数,此函数会调用即时编译器,并将自身的地址作为参数传给编译器。即时编译器则从DLL或EXE中获取相应的IL,编译成机器语言,并将内存中的原零时调用函数替换成机器语言。这个过程的思想,是用已编译的方法调用未编译的方法,实质上被调用的是stub函数;stub函数再调用编译器,将自身编译为本地机器语言;最后,.NET会重新调用该方法,方法此时才被真正地执行。函数被反复调用时,机器指令会被直接执行,而只由编译器对方法进行初次编译需要花费时间。至于那些没有被调用的方法,则不会被编译。
当编译器生成一个EXE文件后, 该程序的入口函数为Main() 方法。装载器将这个EXE 文件载入,探测到该这是一个托管EXE,于是又载入 .NET运行时库文件(包括即时编译器), 接着调用了EXE 的Main() 方法。这将触发对Main()方法的即时编译, Main()方法在内存中被替换为本地机器语言,于是 .NET应用程序开始运行。在被编译为本地语言后,应用程序便可以自由调用本地代码了。当程序中止时,本地代码从内存中释放,所以在下次运行时,IL需要被即时编译器重新编译。
CTS
类型规范,规定了类型必须如何定义才能被CLR承载。內建的CTS数据类型:
CLS
不同的语言有不同的表示,理想情况是所有支持.NET的语言都有一个可遵循的基准。描述了支持.NET的编译器必须支持的最小的和完全的特征集。以生成可由CLR承载的代码。
mscoree.dll工作流
.NET命名空间举例
Microsoft根命名空间
包含的类型用于和那些只属于Windows操作系统的服务进行交互。
.NET SKD
配置Path或者 开始-所有程序-Vs-VS Tools-开发人员命令提示
csc /t:exe[/target:exe] 1.cs
csc 1.cs(默认)
csc /t:library 1.cs(输出dll)
响应文件
外部引用
/r:System.Windows.Forms.dll
输出
/target:exe /out:TestApp.exe *cs
默认响应文件csc.rsp(与csc.exe同目录),包含大部分常用程序集引用。默认被csc.exe引用。可以简写为:
csc /out:TestApp.exe *.cs
##
第二/三部分:C#核心编程结构/C#面向对象编程
char的静态方法
1 | char.IsDigit; //是否为数字 |
改变枚举底层存储类型
1 | enum EmpType |
可以节省内存。但小心溢出。可以定义未核心系统类型(byte,short,int或long)。
获取底层存储类型:
1 | Enum.GetUnderlyingType(typeof(EmpType)) -> System.Int32 |
获取名值对:
1 | var ret = Enum.GetValues(typeof(EmpType)); |
System.ValueType
枚举,结构都隐式派生自此类型。分配在栈上运行。
浅/深复制
当一个值类型中包含一个引用类型。如结构中包含对象引用,直接等号赋值时会有两个独立的结构,内部生成一个引用的副本,指向内存中同一个对象的引用。(浅复制)。
默认访问修饰符
类型成员(变量,方法),是隐式【私有】的。类型(对象)是隐式【内部】的。
1 | class Radio //内部 |
Const,readonly,static
const(常量)=readonly(只读)+static(静态)。
编译期确定 运行期确定
都不可再更改。
##
第四部分:高级C#编程结构
delegate关键字
使用此关键字创建委托时,就间接声明了一个派生自MulticastDelegate的类。不会直接派生自这些基类,但这个类“是”MulticastDelegate。
委托
一个委托对象维护一个可调用方法的列表,而不是一个单独的方法。添加方法,使用+=即可。编译器会将+=转换为Delegate.Combine()方法调用。+=更简洁,-=则转换为Delegate.Remove()。
event事件
事件会扩展为两个隐藏的公共方法,一个add_xx,一个remove_xx。调用者仅需使用+=和-=操作符(操作符将在后台触发正确的add_xx和remove_xx方法,来对原委托进行操作)。
EventHanlder
自定义EventArgs的泛型委托:
1 | class My |
不再需要定义一个自定义委托类型。
操作符重载的思考
通常仅在构建原子数据类型时才有用。文本,点,举行,分数等都是操作符重载的很好候选。人,经理,汽车,数据库连接等却不是。如果一个重载操作符会使用户更难于理解该类型的功能,那就别用它。
Try catch的CIL实现
对一个实现了IDispose对象的using{}语句块会被CIL解析为一个try{}finally{}块。确保Dispose的执行。
new关键字的CIL实现
使用new时,等同于CIL指令newobj:
执行:
- 计算分配对象需要总内存数
- 检查托管堆,如果空间足够,则调用类型构造函数,最终将内存中新对象的应用返回给调用者。它的地址恰好是下一个对象的上一个位置。如果空间不足,则执行一次垃圾回收来尝试是否内存。
- 返回引用之前,移动下一个对象的指针,指向托管堆上的下一个可用的位置。
自动属性的CIL实现
自动属性的get;set;标记会在元数据中被编译器映射成一个私有字段Name和两个方法get_Name(),set_Name()。
应用程序根
根(root)就是一个存储位置,保存着对托管堆上对象的引用。垃圾回收时,CLR判读托管堆上的对象,判断应用程序是否仍然可访问它们(是否还有根)。为此,CLR建立一个对象图。不可访问的对象被标记为终结(finalize,垃圾),它们就会从内测中清除。此时,堆上剩余的空间被压缩调整,引起CLR修改活动应用程序根的集合,指向正确的内存位置。下一个对象的指针被重新调整指向下一个可用位置。
对象的代
设计思路:对象在堆上存在的时间越长,他就更可能应该保留。(WinForm主窗口)最近才放在堆上的对象可能很快就不可访问了。(一个方法中创建的对象)。
第0代:从没有被标记为回收的新分配对象。
第1代:在上一次垃圾回收中没有被回收的对象(曾被标记为回收,但因为已经从其他对象获取了足够的堆空间而没有被删除)。
第2代:在一次以上的垃圾回收后仍然没被回收的对象。
通过给对象赋一个表示代的值,尽快的删除一些较新的对象(本地变量),而不会经常打扰一些旧对象(主窗体)
强制垃圾回收
1 | GC.Collect(); //强制回收所有代 |
适用:
- 程序将进入一段代码,后者不希望被可能得垃圾回收中断。
- 刚刚分配分厂多的对象,想尽可能多地删除已获得的内存。
GC测试
1 | //输出堆上估计得字节数 |
资源包装器
引用托管或非托管宝贵的资源(数据库,文件句柄等)时,希望尽可能快的释放资源。而不能依靠GC的回收。
1 | class MyResourceWrapper : IDisposable |
测试:
1 | ~MyResourceWrapper() |
Lazy 延迟加载
1 | class Program |
第五部分:用.NET程序集编程
.NET程序集的作用
- 促进代码重用。一个代码库(.dll或.exe)被打包后,可以被外部应用程序调用。.NET平台允许我们以语言无关的方式来重用其中的类型,如C#创建,其他.NET语言调用。
- 确定类型便捷。类型的完全限定名需要加上类型所在程序集。如两个不同程序集中命名空间相同的类,被认为是不同的。
- 可版本化的单元。每个程序集被分配一个
. . . 的四部分数字版本号(默认1.0.0.0,默认VS项目设置)。版本号加上可选的公钥值(即程序集强名)使一个程序集的不同版本可在同一台机器共存而不冲突 。通过强名,CLR保证客户端调用程序能加载正确版本的程序集。 - 自描述。程序集记录了清单数据(自身运行需要的外部程序集),还包含了一些元数据(程序集包含的每一个类型的组成,成员名称,接口,基类,构造等)。由于信息被详细的记录了,CLR不需要访问注册表来解析程序集的位置(区别于与微软原来的COM编程模型)
- 可配置的。程序集可“私有”或“共享”。私有:与调用它的客户端应用程序处于同一个目录或子目录下,共享:被部署在全局程序集缓存(GAC)的特定目录中。通过编写XML的配置文件,CLR可以指定位置朝赵,加载指定版本的程序集。查阅本地,网络或URL上的任意目录。
程序集和托管模块
一个团队(程序集),是一个逻辑单位。里面有很多成员(托管模块),成员分2类:
普通成员(托管模块,后缀.netModule。持有元数据和CIL代码)。
领导成员(主模块,后缀.exe或.dll。持有程序集清单),了解每个成员。
一般与这个团队(程序集)沟通或合作是面向它的领导(主模块),同样以领导名称来称呼这个团队。
C#生成的程序集一般为单模块程序集(只有主模块)。
.NET程序集的格式
(.dll或.exe)包含:
- Windows文件首部
可被Windows操作系统加载,标识了应用程序以什么类型(控制台,图形用户界面还是*.dll代码库)驻留于Windows操作系统中。 - CLR文件首部
使CLR了解托管文件的布局,位置和强名等 - CIL代码
独立于平台和CPU的中间语言,运行时才被JIT变异成特定平台和CPU的指令。 - 类型元数据
内含类型和外部应用类型的格式。 - 程序集清单
记录了每一个模块,版本和引用的外部程序集。 - 可选的嵌入资源
图标,图像,声音,字符串表。卫星程序集(附属程序集)可构建国际化软件系统。
代码中的每个字符串字面量都记录在元数据的User Strings 标记下,所以不能储存敏感信息。
私有程序集
应用程序添加外部程序集引用的时候,把副本文件放在本地bin文件夹下。调用时,CLR只是加载本地的副本。并不查询系统注册表。
配置私有程序集:
Test.exe //一个Console控制台程序
Carlib.dll //控制台会调用的程序集
以下三种情况会正常运行:
- 把Test.exe和Carlib.dll放在同一个文件夹下
- 找不到Carlib.dll的文件,会查找同一个文件夹下具有相同友好名称的可执行程序集.exe文件
- 找与程序集同名的文件夹下是否有此程序集。如Carlib/Carlib.dll
- 如果有Test.exe.config配置文件,CLR会根据配置的“规则”探测指定文件夹下是否有此程序集
例:
1 | <?xml version="1.0" encoding="utf-8" ?> |
如果还是找不到,会引发FileNotFoundException异常。
VS的App.config会在编译的时候,把App.config的数据复制到bin/debug/文件下,并改一个合适的名称。如Test.exe.config,FrontWeb.dll.config。
共享程序集
例如mscorlib.dll,旨在多个项目中服用的类型的集合,共享程序集的一个副本可供一台机器上的多个应用程序使用,是机器级别的类库(machine wide)。
全局程序集缓存:
.NET 3.5以前的位置在,C:\Windows\assembly下。
.NET 4.0以上的在C:\Windows\Microsoft.NET\assembly\GAC_MSIL下的v4.0_major.minor.build.revision_publicKeyTokenValue子文件夹下。例如:v4.0_4.0.0.0__b77a5c561934e089。
强名称
- 需要.NET的sn.exe工具生成公钥/私钥对。生成一个.snk(Strong Name Key)文件。C#编译器确定.snk文件的位置后,会在编译时把公钥值记录在程序集清单的.publickey标记中。
- 产生一个基于整个程序集内容(CIL代码,元数据等)的散列值(某一固定输入的独一无二的数值输出,如果更改了程序集的内容,就算只一个字符,陈胜的讲师完全不同的散列码)。散列码结合私钥组成数字签名,并把它嵌入到程序集的CLR首部数据中。
用命令行生成强名:
developer Command Prompt下
sn -k 文件名.snk
再在AssemblyInfo.cs文件中指定位置
[assembly: AssemblyKeyFile(@"C:\MyTestKeyPair.snk")]
编译时,公钥被压缩成散列值加入到清单中 .publicKeyToken标记
用VS生成强名:
Properties -> 签名 ->新建
便会在根目录下生成一个*.snk文件
在GAC中安装强名称的程序集
使用Developer Command Prompt定位到*.dll所在目录(bin\debug)
使用
gacutil -i test.dll/test.exe //安装
gacutil -l test //核实
gacutil -u test //卸载
VS引用程序集时,如果清单中含有.publickey值时,VS会嘉定这个具有强名称的程序集以备部署到GAC中,那么引用的属性窗口中的,复制本地就为false。不会对该程序集进行复制到本地debug文件夹中的工作。
配置共享程序集
发布了1.0.0.0版本后,添加了一些新的功能,产生了2.0.0.0版本。如果客户端应用程序想要使用2.0.0.0版本的新功能,可以把2.0.0.0版本发布到目标机器,让2和1版本和平共处。需要的时候修改*.config文件,动态的定向到2.0.0.0版本。这一切并不需要重新编译和部署。
动态重定向到共享程序集:
1 | <dependentAssembly> |
定义了程序清单当前指向的版本(oldVersion)和GAC中的替代版本(newVersion)
元素
用于指示CLR探测位于任意位置(网络重点,其他本地目录)的依赖程序集。当指向远程计算机时,相关程序集会下载到GAC的下载缓存中。通过此元素加载的程序集必须具有强名称。
例:
<codeBase version="2.0.0.0" href="http://www.MySite.con/Assemblies/CarLibrary.dll" />
Type类型
1 | Car c = new Car(); |
动态加载程序集
1 | Assembly asm = Assembly.Load("Carlib"); //需要保证程序集二进制文件在Debug文件夹下 |
反射共享程序集
1 | string displayName = @"System.Windows.Form.dll,Version=4.0.0.0,PublicKeyToken=b77a5c561934e089,Culture="""; |
晚期绑定
1 | Assembly asm = Assembly.Load("CarLib"); //无需引用,只需把程序集文件放在Debug文件夹下 |
关键在于,在没有某个程序集清单数据的情况下,创建程序集的类型实例。
特性
用于类型,成员,程序集或模块的代码注解。把更多的元数据嵌入到程序集中。
程序集级别特性
使用[assembly:]标签,给特定程序集所有类型应用特性。例:[assembly: CLSCompliant(true)],强制所有程序集中的公共类型符合CLS。
构建可扩展的应用程序
比如VS,开发的时候允许其他软件提供商向IDE开发环境中插入各种“钩子”(自定义模块),一个可能得思路如下:
- 可扩展的应用程序必须提供一些输入手段,允许用户指定被插入的模块。这需要动态加载。
- 必须要确定模块是否支持正确的功能(一组需要的接口)。这需要反射。
必须获取一个需要的基础架构的引用(例如接口类型)并调用成员触发底层功能。这经常需要晚期绑定。
构建每个插件对象需要实现的类型定义
应用于插件对象和承载应用程序,保证功能统一。
1 | namespace CommonSnappableTypes |
- 构建C#插件
1 | namespace CSharpSnapIn |
- 构建承载的应用程序
1 | namespace ConsoleApplication2 |
dynamic
允许我们在类型安全的强类型世界里使用校本化的行为。可以认为dynamic关键字是一个特殊形式的System.Object。但提供了更多特性,并且动态数据不是强类型的,或者说动态数据不是静态类型。
作用范围:字段,属性,返回值,参数。
调用动态声明的数据成员
动态数据不属于静态类型,和js一样,直到运行时你才会直到所调用的动态数据是否支持指定的成员,参数以及成员的拼写是否无误等。编译时不能触发智能感知提示。
dynamic实际用途
构建一个需要大量使用后期绑定(通过反射)的.NET应用程序时。或者构建一个需要与遗留的COM库(如Office)进行交互的.NET应用程序。
使用dynamic可以大大减少打字时间,简化代码。但代价是失去了类型的安全性。
DLR(dynamic language runtime,动态语言运行时)的特性
- 机器灵活的代码库,重构时不需要频繁修改数据类型。
- 不同平台和语言所构建的对象类型之间进行互操作非常简便。
- 可以在运行时为内存中的类型添加或移除成员。
使用dynamic简化晚期绑定
Type type=...;
通过反射
1
2
3
4object obj=Activator.CreateInstance(type);
MethodInfo mi=tpe.GetMethod("Run");
object[] param=10,20;
mi.Invoke(obj,param);通过dynamic
1
2dynamic obj=Activator.CreateInstance(type);
obj.Run(10,20);
简化了查询程序集元数据和打包参数等操作。
互操作程序集
IDE以COM库为基础生成的全新程序集,包含了COM元数据的.NET描述和一小部分将COM事件转换为.NET事件的代码。封装,避免了内部COM的复杂性。
CLR(如果使用dynamic就是DLR)自动对.NET数据类型和COM类型进行映射,因此可以直接在C#代码中使用互操作程序集。在后台,使用RCW(Runtime Callable Wrapper,一个动态生成的代理)对数据进行封送,使其在.NET和COM应用程序之间交互。
主互操作程序集
许多COM库供应商提供了一个“官方”的互操作程序集,成为主互操作程序集,简称PIA。PIA是优化的互操作程序集,比通过IDE添加COM库引用生成的代码更整洁,也更具扩展性。
PIA存在的情况下,添加应用一个COM库,VS会使用提供的PIA,而不是生成一个新的互操作程序集。
嵌入互操作元数据
添加COM库(PIA或其他)时,IDE会自动将该库的“嵌入互操作类型”置为true。
这样,必要的互操作元数据(你真正使用的那部分,而不是全部)已经硬编码到.NET程序中,我们就不必在.NET应用程序中携带互操作程序集的副本了。减少了安装包的尺寸,客户端计算机也不必包含改互操作程序集的副本。
表达式树
当使用DLR进行处理动态数据时,自动创建的“表达式树”将被传递给正确的动态语言帮顶起。帮顶起解析表达式树并传递给正确的对象成员。
进程
一个正在运行的应用程序的固定的安全的边界。用来描述一组资源(外部代码库和主线程)和程序运行所必须的内存分配。
应用程序域:对该进程的逻辑细分。一个应用程序域进一步被细分成多个上下文边界,用来分组目的相似的.NET对象。
进程,应用程序域,上下文的关系:
线程:进程中的独立的基本的执行单元。每一个进程都有一个(在可执行入口处创建的)主线程和其他包含以编程方式创建的额外线程。
进程和线程的关系:当一个线程的时间片用完的时候,会被挂起,以便执行其他线程。线程把挂起前的情况写到线程本地存储中(Thread Local Storage,TLS),并且它们还要获得一个独立的调用栈(call stack)。
Process类
1 | using System.Diagnostics; |
查看进程中的模块
1 | var proc = Process.GetProcessById(10284); |
编程方式启动或结束进程
1 | var proc = Process.Start("Chrome", "www.baidu.com"); |
ProcessStartInfo启动进程
1 | ProcessStartInfo info = new ProcessStartInfo |
应用程序域
传统的非托管程序直接承载在进程上,而.NET可执行程序承载在进程的一个逻辑分区中,即应用程序域(AppDomain)。运行在某个应用程序域中的应用程序将无法访问其他应用程序域中的数据(无论是全局变量还是静态字段),除非使用分布式编程协议(如WCF)。好处在于:
- 应用程序域是.NET平台操作系统独立性的关键特性。将不同操作系统表现加载可执行程序的差异抽象化了。
- 和一个完整的进程相比,应用程序域的CPU和内存占用都有小的多。因此CLR加载和卸载应用程序域比起来完整的进程来说也快得多,并且可以快速提升服务器应用程序的可扩展性。
- 应用程序域为承载的应用程序提供了深度的隔离。如果进程中一个应用程序域失败了,剩余的应用程序域也能保持正常。
.NET平台不允许从内存中卸载指定的程序集。已编程方式卸载库的唯一方式是使用Unload()方法销毁承载的应用程序域。
默认程序域
一个.NET可执行文件启动时,CLR会自动将其放置到宿主进程的默认应用程序域中。
访问当前线程的应用程序域
1 | AppDomain app = AppDomain.CurrentDomain; |
应用程序域中加载的.NET程序集
1 | AppDomain app = AppDomain.CurrentDomain; |
接收程序集加载通知
1 | AppDomain app = AppDomain.CurrentDomain; |
创建新的应用程序域
1 | AppDomain app = AppDomain.CreateDomain("Second"); |
上下文边界
给定对象“特定的家”,一个进程定义了默认的应用程序域,每个应用程序域都有一个默认的上下文(总是第一个创建),也成为上下文0(context 0)。用于组合那些对上下文没有具体的或唯一性需求的.NET对象(大多数对象)。
不需要指定特定上下文的.NET类型称为上下文灵活(context-agile)对象,可以从应用程序域的任何位置访问,与对象的运行时需求没有关系。
需要指定特定上下文的对象称为上下文绑定(context-bound)对象,只能在其被创建的那个上下文中正常运行。必须派生自System.ContextBoundObject基类。
定义上下文绑定对象
自动线程安全的类。
1 | using System.Runtime.Remoting.Contexts; |
研究上下文
1 | using System.Runtime.Remoting.Contexts; |
CIL
.NET平台语言(C#,VB,F#等)的母语,某个语言相关联的编译器会把源代码翻译成CIL
CIL标记分为3类
- CIL指令
以.前缀,例如:.namespace、.class、.pulickeytoken、.method、.assembly - CIL特性
例如:public,extends(指定这个类型的基类),implements(支持的一系列接口) - CIL操作码
例如:ldstr(LoadString,定义一个字符串变量),实际上是操作码的助记符。真正的操作码是二进制码,add - 0x58,sub - 0x59,newobj - 0x73。
CIL基于栈的本质
CIL不允许直接访问一个数据(本地变量、参数变量或属性)。而是通过虚拟执行栈来访问。
1 | string myMessage="Hello"; //分为1-3 |
- .locals init ([0] string myMessage)
定义一个本地字符串变量(在Call Stack本地变量索引0处) - ldstr “Hello”
将字符串的引用放在了Envaluation Stack中,而真正的字符串放在了Managed Heap中 - stloc.0
将Envaluation Stack中的值保存到 Call Stack中索引为0的本地变量中(V0,栈低已存在args[最底]和return address),因为Envaluation Stack中存放的是“hello”字符串的地址,所以变量中存放的也是字符串的地址。 - ldloc.0
把Call Stack中索引0处变量的的值(引用地址),加载到Envaluation Stack中 call void [mscorlib]System.Console::WriteLine(string)
从 Evaluation Stack 中取出一个值,此值为 Reference Type,调用方法Managed Heap(托管堆):这是动态配置(Dynamic Allocation)的记忆体,由GC在执行时自动管理,整个 Process 共用一个 Managed Heap,可以理解为引用类型的东西都放在这个Managed Heap中。
- Call Stack(调用栈):CLR在执行时自动管理的记忆体,每个Thread都有自己的Call Stack堆栈。每调用一次method,就会使得Call Stack上多了一个Record Frame;调用完毕之后,此Record Frame会被丢弃。一般来说,Record Frame内记录着method参数(Parameter)、返回位址(Return Address)、以及局部变量(Local Variable)。CLR使用零基索引的方式来识别局部变量。
- Evaluation Stack(虚拟执行栈):这是由 CLR在执行时自动管理的记忆体,每个Thread都有自己的Evaluation Stack。压入的到Evaluation Stack的值,当方法调用结束时必须保持这个堆栈的平衡(清空),这里面存放例如局部变量值,以及引用类型的地址。如果忘记清空,ilasm仍然会编译成功。可以用peverify.exe工具诊断。peverify test.dll
- ld,加载,用于压栈到虚拟执行栈
- st,存储,用于弹栈虚拟执行栈顶的值到内存调用栈中
CIL的代码标签
例如:IL_0001:,IL_000c。大多数标签是可选的,完全可以移除。只有编写有多个分支和循环结构的CIL代码时,通过这些标签指定逻辑流转到哪里的时候,才是必需的。
使用ilasm.exe编译CIL代码
ilasm /exe Hello.il /output=NewAssembly.exe
-?查看选项参数
CIL指令和特性
- 指定外部应用程序集
1 | .assembly extern mscorlib |
- 定义当前程序集
1 | .assembly CILTypes |
- 定义命名空间
1 | .namespace MyNamespance{} |
- 定义类类型
1 | .namespace MyNamespace |
- 定义和实现接口
1 | .class public interface IMyInterface{} |
- 定义结构
1 | .class public sealed MyStruct extends [mscorlib]System.ValueType{} |
简化:
1 | .class public sealed value MyStruct{} |
- 定义枚举
1 | .class public sealed MyEnum extends [mscorlib]Sytem.Enum{} |
简化:
1 | .class public sealed enum MyEnum{} |
- 定义泛型
C#:
1 | List<int> myIntes=new List<int>(); |
CIL:
1 | newobj instance void class [mscorlib]Sytem.Collections.Generic.List`1<int32>::.ctor() |
- 定义数据字段
1 | .class public sealed enum MyEnum |
私有成员
1 | .class public MyBaseClass |
- 定义属性
1 | .class public MyBaseClass |
- 定义构造函数
1 | .class public MyBaseClass |
- 定义参数
C#:
1 | public static void MyMethod(int inputInt,ref int refInt,ArrayList ar,out outputInt) |
CIL:
1 | .method public hidebysig static void MyMethod(int32 inputInt, |
.NET 基础类库、C#和CIL数据类型的映射
.maxstack指令
确定一个在方法执行阶段可以被压入栈中的最大变量数目。默认值是8
在CIL中映射参数到本地变量
C#:
1 | public static int Add(int a,int b) |
CIL:
//已简化
1 | .method public hidebysig static int32 Add(int32 a,int32 b) cilmanaged |
this隐式引用
虚拟执行栈索引是从0开始。在使用CIL代码时,任何非静态函数在接收传入参数时都自动隐式地接受了一个附加参数,即当前对象的引用(this)。
C#:
1 | public int Add(int a,int b) |
CIL://伪代码,this是隐式的接受的
1 | .method public hidebysig static int32 AddTwoInt(MyClass_HiddenThisPointer this,int32 a,int32 b) cil managed |
循环结构
C#:
1 | public static void CountToTen() |
CIL:
1 | .method public hidebysig static void COuntToTen() cil managed |
动态程序集
静态程序集:存在于磁盘的.NET二进制文件
动态程序集:运行中通过使用System.Reflection.Emit命名空间提供的类型在内存中创建程序集及其模块,类型定义以及CIL实现逻辑。并可以保存到磁盘上生成一个新的静态程序集。
用处:
构建需要根据用户输入来生成程序集文件的.NET开发工具
构建需要在运行时通过元数据来生成远程类型的代理的程序
加载静态程序集并能动态插入新类型到二进制图像中。
System.Reflection.Emit命名空间的成员
ILGenerator
注入CIL操作码到一个给定的类型成员。
产生动态程序集,保存并实现延迟绑定
1 | class Program |
第六部分:.NET基础类库
应用程序域和线程
一个应用程序域可以承载多个线程,线程可以跨越不同的应用程序域
1 | AppDomain ad=Thread.GetDomain(); |
获取正在承载当前线程的应用程序域
1 | Context ctx=Thread.CurrentContext; |
获取当前操作线程所处的上下文
委托的异步性
1 | class Program |
Threading命名空间
AutoResetEvent类
线程同步的通知类
1 | public static AutoResetEvent waitHandle = new AutoResetEvent(false); |
TimerCallBack
定时任务
1 | static void Main(string[] args) |
ThreadPool的好处
1,减少了线程创建、开始和停止的次数,这提高了效率
2,能够使我们将注意力放到业务逻辑上而不是多线程架构上
某些情况仍应优先使用手工线程管理:
1,需要前台线程或设置优先级别,线程池中的线程总是后台线程,优先级是默认的。无法修改
2,需要一个带有固定标识的线程便于退出、挂起或通过名字发现它。
Parallel类
1 | class Program |
次线程访问UI线程的控件
1 | this.Invoke((Action)delegate |
WinForm中,线程创建的控件具有“线程相关性”。次线程不能直接访问主线程(UI线程)创建的控件,可以通过此方法传递异步委托执行。
async await关键字
1 | class Program |
返回void的异步方法
返回非泛型的Task类,并忽略return
1 | private asynv Task MethodVoidAsync() |
多个await的异步方法
1 | public async void Entry() |
System.IO
获取计算机上驱动器的细节
1 | DriveInfo[] drives = DriveInfo.GetDrives(); |
StreamWriter和StreamReader
1 | using (StreamWriter writer = File.CreateText("1.txt")) |
等于
1 | using (StreamWriter writer=new StreamWriter("1.txt")) |
1 | using (StreamReader reader = File.OpenText("1.txt")) |
等于
1 | using (StreamReader reader=new StreamReader("1.txt")) |
BinaryWriter类
允许我们从基层流中以简洁的二进制格式读取或写入离散数据类型
1 | using (BinaryWriter bw=new BinaryWriter(File.OpenWrite("1.txt"))) |
文件监视
1 | FileSystemWatcher watcher = new FileSystemWatcher |
对象序列化
1 | [ ] |
对象图
持久化对象的格式
1 | using System.Runtime.Serialization.Formatters.Binary; |
XmlSerializer和SoapFormatter
XmlSerializer:持久化为XML文档,只会保存公有已赋值字段或公共属性。可以保存偏好设置(颜色,文字大小等)。
SoapFormatter:持久化为一个SOAP消息(传递消息到Web服务或从Web服务传递消息的标准XML格式)。
1 | static void Main(string[] args) |
格式化程序中的类型保真
BinaryFormatter,持久化对象图中对象的字段数据,和每个类型的完全限定名城和定义程序集的完整名称(强名)。这些数据使跨越.NET应用程序机器边界传递对象成为理想的选择。
SoapFormatter,XmsSerializer,没有序列化完整的.NET类型元数据(完全限定名称或程序集),可用于标准的.NETWeb服务,可被任何平台中的客户端调用(Windows、MAC OS X和Linux)。
控制生成的XML数据
1 | static void Main(string[] args) |
生成的XML部分如下:
1 | <?xml version="1.0"?> |
使用特性定制序列化
1 | [ ] |