14 .net程序集入门
程序集是基本的部署单元
- 定义自定义命名空间
一个程序集可以有多个namespace,一个namespace可以包含多个程序集
namespace偏向逻辑上的组织,程序集可以看作是物理文件上的关系
不同namespace之间的引用通过 using
完全限定名: CIL中总是以完全限定名进行定义
MyCircle mycircle = new MyCircle();
MyNameSpace.Circle circle = new MyNameSpace.Circle();
Circle defalutCircle = new Circle();
别名:
using MyCircle = MyNameSpace.Circle;
嵌套的命名空间
namespace Nest.ClassLibraryforCSharp
程序集的作用
.net程序可以由多个程序集拼装而成
程序集是以公共语言运行库为宿主的,版本化的,自描述的二进制文件
程序集往往和二进制文件拓展名(.dll,.exe)一致.但是两者内部构成完全不同.
程序集促进代码重用
代码库(类库)是.dll/.exe等形式的文件..net平台允许我们以语言无关的方式重用代码库中的类型.
即,c#的代码库中的接口可以被VB语言实现
程序集确定类型边界
不同程序集中的完全限定名即使相同也是不一致的,逻辑也上不属于同一个namespace
程序集可版本化
自描述
可配置程序集的格式
- 构建和使用自定义类库
清单
manifest:描述运行所必需的所有外部程序集
AssemblyInfo.cs
CIL 独立于平台,当.net运行库把程序集加载到内存是,JIT编译器会编译CIL为目标平台可以理解的指令
类型元数据 - 私有程序集
私有程序集要求放置在客户端应用程序所在目录或其子目录下
探测过程:把外部程序集请求映射到对应的二进制文件的过程.
配置私有程序集: XX.exe.config
App.Config文件: 编译项目时会自动改变名字成XX.exe.config - 共享程序集
共享程序集的一个副本可供一台机器的多个应用程序使用
位置在GAC中:
1.0 - 3.5 C:\Windows\assembly
> 4.0 C:\Windows\Microsoft.NET\assembly 强名称,类似git
发布共享程序集
生成强名称
安装入GAC via gacutil.exe
cd C:\MyCode\CarLibrary\bin\Debug
gacutil –i CarLibrary.dll
gacutil –1 CarLibrary - 发行者策略程序集
允许程序的发行者将.config的二进制版本安装到GAC.这样客户端程序就不需要.config文件了 - 元素
用于指定代码库,只是CLR探测任意位置(网络终点)的依赖程序集 - System.Configuration
以编程的方式读取配置文件的数据.config
15 类型反射,晚期绑定和基于特性的编程
System.Reflection在编程时获取底层代码
- 元数据
元数据可以完整的描述类型:类,接口,结构,枚举和委托 - 反射
.net中反射是一个发现运行时类型的进程.
通过反射,可以得到dll/exe程序集所包含的所有类型的列表,包括给定类型定义的方法,字段,属性和事件等
System.Type
其中定义了许多成员用来检查某个类型的元数据
Type是抽象类,不能直接用new创建.
Object.GetType()获取当前对象元数据的Type类的实例
typeof关键字可以不建实例,但需要强类型名称
Type.GetType()可以通过强类型名称字符串(完全限定名)
Type type = square.GetType();
type = typeof(Square);
type = Type.GetType("Square",false); 构建自定义的元数据查看器
修饰限定符会对是否显示元数据有影响var methods = from f in type.GetMethods() select f.Name; foreach (var v in methods) Console.WriteLine(" reflect method is {0}",v); var fileds = from f in type.GetFields() select f.Name; foreach (var v in fileds) Console.WriteLine(" reflect fileds is {0}", v); var pros = from f in type.GetProperties() select f.Name; foreach (var v in pros) Console.WriteLine(" reflect pros is {0}", v);
动态加载程序集
加载并发射在编译时并不知道的程序集
动态加载:按需加载外部程序集的操作//path D:\Git\SourceCode\C#\ConsoleApps_ClassLibrary\ClassLibraryforCSharp\ClassLibraryforCSharp\bin\Debug\Car.dll //Load 只探测客户端文件夹 //assembly = Assembly.Load(assemblyname); //assembly = Assembly.LoadFrom(path); assembly = Assembly.LoadFile(path); DisplayTypesInAssembly(assembly); Console.WriteLine("FullName " + assembly.FullName); foreach (Type t in assembly.GetTypes()) Console.WriteLine("type is {0}", t);
反射共享程序集
本地化程序集: 指定一个区域设置Assembly assembly = Assembly.Load(@"Car, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"); 共享程序集: 版本号和公钥标记值 GAC中共享程序集必须指定公钥标记值 assembly = Assembly.Load(@"mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
晚期绑定
一种创建一个给定类型的实例并在运行时调用其成员,而不需要再编译时知道他存在的的技术System.Activator Assembly assembly = Assembly.Load("ClassLibraryforCSharp"); Type type = assembly.GetType("ClassLibraryforCSharp.Square"); object obj = Activator.CreateInstance(type);
调用没有参数的方法
晚期绑定创建的对象,在编译之前是未知的对象.因为没有引入对应的dll,所以强转也行不通.这时,结合反射就可以了MethodInfo methodInfo = type.GetMethod("ShowMessage"); methodInfo.Invoke(obj,null);
调用有参方法
methodInfo.Invoke(obj, new object[] { “custome message ” });
可选参数也必须指定methodInfo.Invoke(obj, new object[] { null }); public void ShowMessage(string message = "default message") { Console.WriteLine("hello, this is message for show : {0} .",message); }
.net特性的作用 attributes
.net编译器的任务之一是为所有定义个引用的类型生成元数据描述.attribute可以吧更多的元数据嵌入到程序集中.
attribute就是用于类型(类,接口,结构,枚举和委托),成员(属性,方法),程序集或模块的代码注解.
派生自基类: System.Attributes
用来嵌入元数据
有代理反射,代理才会实际生效
使用格式:[Attributes][NonSerialized,Obsolete] public string NameFiled2;
自定义特性
出于安全性考虑,自定义特性设计为密封类 sealed[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct|AttributeTargets.Method, Inherited = true)] public sealed class VehicalDescriptionAttribute : Attribute
命名属性语法
[VehicalDescription(Color = "Red", Description = "trunk")]
程序集级别特性
[assembly: CLSCompliant(true)] namespace ClassLibraryforCSharp AssemblyInfo.cs: 存放程序集级别的特性
使用早期绑定反射特性
特性知道另一个软件反射他的值时才有用Type type = typeof(Vehical); foreach (VehicalDescriptionAttribute v in type.GetCustomAttributes(false)) Console.WriteLine("attribute : {0}",v.Description);
使用后期绑定反射特性
后期绑定是在编译时拿特性或者方法去指定的对象找结果//late binding Assembly assembly = Assembly.Load("ClassLibraryforCSharp"); Type vehicalType = assembly.GetType("ClassLibraryforCSharp.VehicalDescriptionAttribute"); object[] objs; foreach (Type t in assembly.GetTypes()) { objs = t.GetCustomAttributes(vehicalType,false); foreach (object o in objs) Console.WriteLine("name {0}, value {1}",t.Name,vehicalType.GetProperty("Description").GetValue(o,null)); }
- 可拓展应用程序
让应用程序提供需要的钩子
动态加载 Assembly: 允许用户指定要插入的模块
反射 Type: 确认插入的模块是否支持正确的功能
晚期绑定 Activator: 获取需要的基础架构的引用并调用成员(Activator.CreateInstance(type);)触发底层功能 - 构建可拓展的应用程序
一个接口+两个实现对象
程序集创建实例后用接口类型进行强转,然后通过接口调用对象的方法
16 动态类型和动态语言运行时
.net 4.0 引入了关键字dynamic,他允许我们在类型安全的分号和花括号的强类型世界里使用脚本化的行为.
DLR dynamic language runtime: 将松散的类型映射到正确的内存对象
dynamic关键字的作用
var是强类型,静态类型 可以定义本地变量,其实际类型取决于编译时初次分配的类型.一旦分配后,他的强类型就不会再改变.
dynamic是动态类型,可以随时改变.编译过程和结果与object完全相同var v1 = "var"; //Cannot implicitly convert type 'int' to 'string' //v1 = 8; object o1 = "object"; o1 = 8; dynamic d1 = "dynamic"; d1 = 8;
DynamicMethod方法可以通过编译但是调用会产生错误
private static void DynamicMethod() { dynamic textData = "Hello"; Console.WriteLine(textData.ToUpper()); Console.WriteLine(textData.toUpper()); Console.WriteLine(textData.Foo("sad", DateTime.Now)); }
dynamic关键字的作用域
var关键字不能用于返回值,参数或类/结构的成员.但dynamic可以
限制
调用动态数据的方法时不能使用Lambda表达式和C#匿名方法.
动态类型无法进行编译检查,不能出发智能感知并且无法进行LINQ查询
实际用途
dynamic是在用类型的安全性来换取代码的简洁度
可用于与遗留的COM库(如,Office产品)进行交互的.net应用程序- DLR的作用
System.Dynamic 位于System.Core.dll
动态语言运行时的特性
极其灵活的代码库.重构时不需要频繁的修改数据类型
跨平台和语言.在不同平台和编程语言所创建的对象类型之间交互操作很简便
运行时操作内存
表达式树 创建有效载荷: 动态方法的名称,参数等信息. 然后传递给运行时绑定器(c#/python/COM) 动态类型简化后期绑定调用
Type typeSquare = Assembly.Load("ClassLibraryforCSharp").GetType("ClassLibraryforCSharp.Square"); MethodInfo methodInfo = typeSquare.GetMethod("ShowMessage"); object objSquare = Activator.CreateInstance(typeSquare); methodInfo.Invoke(objSquare, new object[] { "" }); dynamic dynSquare = Activator.CreateInstance(typeSquare); dynSquare.ShowMessage();
- 动态类型简化COM互操作
.net程序与COM对象通信,首先生成互操作程序集.互操作程序集除了小部分将COM事件转换为.net事件的代码之外,不含实现代码.使.net代码库避免了内部COM的复杂性.
RCW runtime callable wrapper 对数据进行封装,实现.net程序和COM程序之间的交互. 动态数据进行COM互操作
Microsoft.Office.Interop.Excel.Application excelApp = new Microsoft.Office.Interop.Excel.Application(); excelApp.Visible = true; excelApp.Workbooks.Add(); Microsoft.Office.Interop.Excel._Worksheet worksheet = excelApp.ActiveSheet; worksheet.Cells[1, "A"] = "Color"; worksheet.Cells[1, "B"] = "Make"; worksheet.Cells[1, "C"] = "Name"; int row = 1; foreach (Car c in carsInStock) { row++; worksheet.Cells[row, "A"] = c.Color; worksheet.Cells[row, "B"] = c.Make; worksheet.Cells[row, "C"] = c.Name; } worksheet.SaveAs(string.Format(@"{0}\Inventory.xlsx", Environment.CurrentDirectory)); excelApp.Quit(); MessageBox.Show("please find Inventory.xslx in app folder", "Export complete");
18 CIL和动态程序集的作用
CIL common intermediate language
- CIL指令,特性和操作码
CIL指令: 一组用于描述.NET程序集总体结构的标记, 语法上用一个(.) 表示
定义程序集的结构及其类型
CIL特性: 可以限定应该如何处理一个CIL指令.不同于.net特性(attribute)
进一步定义一个给定指令
public特性:建立这个类型的可见性
extends特性:明确类型的基类
implements特性:列出类型支持的一系列接口
CIL操作码:提供类型的实现逻辑: box,throw,sizeof,ldstr(load string)
用来实现类型成员
JIT编译器处理的CIL代码是二进制数据,每个二进制CIL操作码对应一个助记符
ildasm.exe就是将二进制的操作码翻译成对应的CIL助记符. 入栈和出栈: CIL基于栈的本质
CIL是一个完全以栈为基础的开发语言, 虚拟执行栈
加载: CIL通过一系列操作码完成压值到虚拟执行栈的过程
存储: CIL通过一系列操作码将栈顶的值一道内存中
CIL不允许直接访问数据,包括本地变量,方法中传入的变量或字段数据.为了实现访问必须显式的加载数据到栈中.class public auto ansi beforefieldinit ClassLibraryforCSharp.CIL extends [mscorlib]System.Object { .method public hidebysig instance void PrintMessage() cil managed { // Code size 15 (0xf) .maxstack 1 .locals init ([0] string message) IL_0000: nop IL_0001: ldstr "hello " IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: call void [mscorlib]System.Console::WriteLine(string) IL_000d: nop IL_000e: ret } // end of method CIL::PrintMessage
正反向工程
将程序集中的CIL文件导出到外部文件中,按需求编辑后重新编译代码.
.assembly extern 标志引用程序集
.class 指令的可选特性.class private auto ansi beforefieldinit ClassLibraryforCSharp.Program14 extends [mscorlib]System.Object .method指令 方法定义 CIL中与.net类型交互,总是需要使用类型的完全限定名 CIL代码标签: IL_0000: 用于指定分支和循环结构的CIL代码的逻辑流 ilasm.exe编译CIL代码 ilasm /exe C:\Users\v-gusong\Desktop\a.il /output=C:\Users\v-gusong\Desktop\new.exe 验证.exe peverify newass.exe
CIL指令和特性
引用外部指令集.assembly extern mscorlib { .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. .ver 4:0:0:0 } 定义当前程序集 .assembly ClassLibraryforCSharp { ... } 命名空间 .namespace Myns{} 类 .class private auto ansi beforefieldinit Nest.ClassLibraryforCSharp.Square extends [mscorlib]System.Object 接口 .class private auto ansi serializable beforefieldinit ClassLibraryforCSharp.Square extends [mscorlib]System.Object implements ClassLibraryforCSharp.ITest 结构 枚举 泛型 通过反勾号`加表示类型参数个数的值来表示
定义类型成员
数据字段 .field public static initonly class ClassLibraryforCSharp.Program14/'<>c' '<>9' 构造函数 实例化层次的构造函数 .ctor 静态的类构造函数.cctor .method private hidebysig specialname rtspecialname static void .cctor() cil managed { // Code size 11 (0xb) .maxstack 8 IL_0000: newobj instance void ClassLibraryforCSharp.Program/'<>c'::.ctor() IL_0005: stsfld class ClassLibraryforCSharp.Program/'<>c' ClassLibraryforCSharp.Program/'<>c'::'<>9' IL_000a: ret } // end of method '<>c'::.cctor 属性 .field private string '<Description>k__BackingField' .method public hidebysig specialname instance string get_Description() cil managed { .custom instance void ClassLibraryforCSharp.VehicalDescriptionAttribute::'<Description>k__BackingField' IL_0006: ret } // end of method VehicalDescriptionAttribute::get_Description .method public hidebysig specialname instance void set_Description(string 'value') cil managed { }
CIL 操作码
.maxstack 被压入栈的最大变量数目
映射参数.method public hidebysig static int32 Add(int32 a, int32 b) cil managed { .maxstack 2 ldarg.0 // Load "a" onto the stack. ldarg.1 // Load "b" onto the stack. add // Add both values. ret } 循环结构 .method public hidebysig static void CountToTen() cil managed { .maxstack 2 .locals init ([0] int32 i) // Init the local integer "i". IL_0000: ldc.i4.0 // Load this value onto the stack. IL_0001: stloc.0 // Store this value at index "0". IL_0002: br.s IL_0008 // Jump to IL_0008. IL_0004: ldloc.0 // Load value of variable at index 0. IL_0005: ldc.i4.1 // Load the value "1" on the stack. IL_0006: add // Add current value on the stack at index 0. IL_0007: stloc.0 IL_0008: ldloc.0 // Load value at index "0". IL_0009: ldc.i4.s 10 // Load value of "10" onto the stack. IL_000b: blt.s IL_0004 // Less than? If so, jump back to IL_0004 IL_000d: ret }
- 构建.net程序集
- 动态程序集
静态程序集:.net从磁盘存储器加载的.net二进制文件,是一些物理文件.
动态程序集:运行过程中System.Reflection.Emit命名空间提供的类型在内存中创建,最后保存到到物理的二进制文件
ILGenerator: 注入CIL操作码到一个给定的类型成员
ModuleBuilder类型的作用: 支持系列成员方法,用来定义模块包含的各种类型(类,接口,结构..)和嵌入资源(pic,table…)