您的位置:首页 > 其它

学习笔记---元数据、程序集、GAC版本控制、属性(Attribute)、反射(利用.NET编译器实现表达式计算器)

2010-12-19 16:01 525 查看
1. 元数据(Metadata): 元数据被存储在程序集中, 用来对程序集中的类型和方法进行描述, 程序集之所以是可描述的就是因为元数据完整的描述了程序集中每个模块的内容. 简而言之, 元数据就是用来描述数据的数据, 即元数据描述代码和数据(如类型、代码、程序集等等信息).

NET应用程序实际上由 代码、数据和元数据组成, 元数据的来源有两个: 属性Attribute(非封装字段用的Property)和.Net编译器(主要来源). 元数据是跟程序存储在一起的(例如: //后便的注释信息在编译时是被忽略掉的, 而Attribute属性的信息是会被添加到程序集中的).

2. 简单讲, 程序集就是.dll或.exe文件. 程序集有两类: 专有程序集(Private Assemblies)和共享程序集(Shared Assemblies), 专有程序集只被一个应用程序使用; 而共享程序集可以被多个应用程序共享.

.NET编程环境中的共享程序集可以由名称和版本号唯一的标识. 使用共享程序集有一个”DLL HELL”的问题, 虽然程序集名称和版本号可以唯一确定一个程序集, 但版本号是个不可预测的值, 在编写代码时候载入的还是程序集的名称, 这样就会出现这么一种情况: 软件A和软件B 开始都用到一个名称为asm.dll(Ver 1,0), 后来A软件的新版本更新了asm.dll为ver2.0 版本. 若用户先安装B再安装A, 由于大多数程序集设计时的兼容性考虑, A和B都可以工作. 但当用户先安装A在安装B, 则B的老版本程序集会覆盖掉A的新版本程序集, 这样软件A就很可能出现错误而无法正常使用(asm.dll ver 2.0添加了新功能). 由此, 引入了一个版本控制(Versions)的问题.

3. 版本控制(Version): 全局程序集缓存(The Global Assembly Cache, GAC)允许同一个程序集的较老版本和较新版本可以同时存在. GAC采用了强名称的手段来解决DLL HELL的问题.

强名称由4个部分组成: 程序集名称、版本号、文化、公钥(数字签名), 这4个组成部分可以在任意项目的properties项的AssemblyInfo.cs文件中看到. 我们在为程序集添加强名称后可以通过类似于C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin目录中的gacutil工具添加到GAC中(命令: gacutil /i AssemblyName.dll), 也可以在添加强名称后, 直接用鼠标拖动AsseblyName.dll释放到C:\Windows\Assembly目下即可(实际上进入Assembly目录后, 文件浏览器讲自动变成GAC工具程序).

再说说, 公钥加密: 公钥加密的本质: 建立两个密钥, 被第一个密钥加密的数据只能被第二个密钥解密, 而第二个密钥加密的数据也只能被第一个密钥解密. 这两个密钥一个叫公钥, 用于向外发布, 任何人都可以得到; 而两外一个叫做私钥, 只有客户机自己知道.

加密通信: 假设服务器发送给客户机的数据, 经过客户端公钥加密后发送, 就只有客户机的私钥可以解密, 这样就实现了数据加密的功能. 但客户机如果在回发加密数据给服务器的话, 客户机需要使用服务器的公钥加密, 这样服务器就可以用自己的私钥解密, 整个过程分别使用了服务器的密钥对和客户机的密钥对. 这便是加密过程.

数字签名: 再看另外一个过程, 若客户机用自己的私钥加密数据后向外发送, 而外网任何一台主机都可以通过客户端的公钥解密数据, 这样看起来好像不能够实现保密功能, 但你有没有想过这个过程保证了数据肯定是客户端发送出来的. 不管有多少人利用公钥解密了客户端私钥加密的数据, 只要他们能解开, 就说明这些数据肯定是客户端发送的, 换句话讲, 在这个过程中, 唯一标示了数据是从客户端发送的, 就好比客户端发送文件的时候签了自己的名字证明是自己发送的. 这就是数字签名的由来.

将自己的dll放到全局程序集缓存(GAC)中的过程:

a. 添加完整的文档注释, 类型及成员均需添加且不可遗漏(类: 描述... ; 方法: 根据...; 属性: 获取或设置... ; 参数: System.xx类型, 表示...)

b. 添加完整程序集信息(通过创建空类库项目, 获得AssemblyInfo文件并修改)

主版本号: 软件版本重大变化, 如: 单机版到网络版

次要版本号: 软件功能变化, 如: 添加辅助挂机功能

内部版本号: 如: beta,final, 或同时做出几个不同方案, 最终选一个

修订号: 修改bug的次数

c. 通过类库中的强名称工具获得签名密钥(需在环境变量的Path中, 添加VS2008的SDK目录c:\program files\Microsoft SDKs\Windows\v6.0A\bin)

cmd中生成强名称(注意大小写): sn -k strongname.snk 查看强名称: sn -T xxx.dll

备注: VS2005的SDK目录为c:\program files\Microsoft Visual Studio 8\SDK\v2.0\Bin

d. 将有完整文档注释的cs文件、AssemblyInfo文件及强名称文件编译成一个dll文件和XML文档

cmd中键入命令: csc /target:library /doc:xxx.xml /keyfile:strongname.snk /out:xxx.dll AssemblyInfo.cs aaa.cs bbb.cs

也可以在VS中右击项目, 选”属性”->”签名”, 使用刚创建的*.snk文件即可

e. 将xxx.dll和xxx.xml拷贝到稳定的目录(如框架类库目录: c:\windows\Microsoft.Net\Framework\v3.5\)下

cmd中使用命令gacutil /i xxx.dll注册到GAC中 使用gacutil /u xxx.dll可以反注册; 若不需要xxx.xml, 可以简单的讲xxx.dll拖到c:\windows\Assemly文件夹中

4. Attribute是一种生成元数据的机制, 用来往程序集中添加描述信息, 即往程序集中添加元数据, 这些添加的信息会随一并保存在程序集中. 可以将Attribute理解为给C#代码添加注释用的, 这种添加注释的手段和//及/**/的区别是, attribute添加的注释会保存到程序集(.dll)文件中, 而//和/**/则会被编译器忽略.

值得注意的是: 在.NET体系(或理解为.NET系统)中, 往往通过Attribute属性影响编译效果, 如: 在Web服务中、序列化时等等. 通过Attribute添加到程序集中的信息可以通过反射读取出来.

5. 反射正是从程序或程序集中读取元数据的(如: 反射的典型应用动态提示就是读取程序集清单(清单(Manifest)是元数据的一部分), Type类是反射的核心, Type类封装了对象类型的表示. 反射的定义指某个程序读取其自身或其他程序的元数据的过程.

再来看看Type类: 刚才说过Type封装了对象类型的表示, 实际上我们每定义一个类(如: Student类)在系统中都会一一对应Type类的一个对象(如: student对象), 而系统使用的正是这个student对象. 可以简单记作: 类型 <---> Type类的对象.

Type类是访问元数据的主要手段, 并且Type类继承自MemberInfo类, MemberInfo类封装了所有类成员的相关信息.

使用typeof关键字将会为我们创建一个Type类的对象, 该Type类的对象用来代表一个类(如: Student类). 听起来好像有点绕, 关键点是 Type类的某个对象会一一对应某个类.

typeof和GetType()的区别: typeof用在已经知道的类型的情况中, 用typeof获得Type类的一个对象, 而GetType()是一个实例方法, 事先不知道该实例的类型, 在运行时才会获得该实例的类型.

读取元数据只是反射的常见任务之一, 反射常用于4种任务:

a. 查看元数据 b. 查看类型(类型发现) d. 对方法和特性的迟绑定(也叫动态激活或运行时绑定) c. 运行时创建类型

关于上边的几个概念间的关系可以参加下图:

代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Caculator
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
InitData();
}

private void InitData()
{
this.txt_exp.Text = "";
this.btn_reset.Enabled = false;
this.txt_result.Text = "";
}

private void btn_caculate_Click(object sender, EventArgs e)
{
this.btn_reset.Enabled = true;
string exp = this.txt_exp.Text.Trim();

BLL.CalculatorBLL cb = new BLL.CalculatorBLL();
this.txt_result.Text = this.txt_exp.Text + " = " +cb.Compute(exp).ToString();

}

private void btn_reset_Click(object sender, EventArgs e)
{
InitData();
}

private void btn_complexCompute_Click(object sender, EventArgs e)
{
this.btn_reset.Enabled = true;
BLL.CalculatorBLL cb = new BLL.CalculatorBLL();
this.txt_result.Text = this.txt_exp.Text + " = " + cb.Calculate(this.txt_exp.Text.Trim());
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: