.NET 框架与架构模式和设计模式详解系列(图解DotNet框架)之一:.Net基础,编译与执行引擎
(一)众所周知,DotNet框架是非常庞大的,光项目创建时的种类就有WPF,WCF,WF这三种最新的技术,还有以前的Web,WinForm,Service,Mobile等等.
这么复杂和庞大的框架,用文字来描述是远远不够的,所以我准备写一系列图文并茂的文章,把我所知道的所有Net框架中的东西全部串联起来,希望可以给大家一个DotNet框架的知识图,本人能力有限,难免有理解错误和表达不清的地方,望大家谅解并指教.
好,我们现在就开始第一张图的学习,再所有的文章写完后,大家将会得到一个完整的框架图。
下面我会做出对这个图的解释,请大家对照图来一步一步的学习。
1.CTS 和 CLS :
CTS:公共类型系统。他是一个集合{类,接口,委托,结构,枚举,类型成员,基本数据类型},只有符合CTS的类型才能在CLR上运行。简单点说就是VB,C#,C++等DotNet支持的语言的类型集合。这些东西大部分都定义在System.dll中,我们将在下一篇来讨论system.dll。
CLS:公共语言规范。从图上可以看出,他是CTS的子集,为什么他是子集呢?因为在C#中有Ulong而VB中就没有。看以下代码
{
public ulong Add(ulong a, ulong b) //如果某语言特有的类型,作为返回类型和参数则不遵循CLS
{
return a + b;
}
public int Add(int a, int b) //但是作为局部变量则仍然遵循CLS
{
ulong x;
return a + b;
}
}
在默认.NET开发平台上是不会执行CLS检查的,如果要开启可以在 AssemblyInfo.cs中加入代码
大家可能要问,为什么要遵循CLS呢?答案在后面给出。
2. CIL,元数据和程序集清单
CIL:公共中间语言。我们大家都知道,在.Net中使用VB写一个DLL,那么在C#的项目中也可以调用,反之亦然。如何实现的呢?就是CIL了,前面我们说过CLS的作用,并且留下了为什么要遵守CLS的疑问,就是因为.net编译生成的dll或Exe,其实主要是CIL,原数据和程序集清单这三部分组成。那么要想VB和C#共享,就要有一个通用的规范,它就是CLS了,呵呵,现在明白为什么要CLS了吧,如果你不去遵守CLS也是可以的,但是就不能保证你的dll的复用性了。
元数据:.net的反射,远程处理,序列化这些东西都很神秘吧,其实他们都是原数据的功劳,你所定义的类,方法,方法的返回类型和参数,属性,字段,都会被编译成元数据,然后当你序列化或反射时,拿这些原数据给你生成一个类。
程序集清单:这个就容易理解了,你添加的dll引用的信息全都在这里。下面是一个Dll中的信息。
原代码:
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
}
}
public class Test
{
public ulong Add(ulong a, ulong b) //如果某语言特有的类型,作为返回类型和参数则不遵循CLS
{
return a + b;
}
public int Add(int a, int b) //但是作为局部变量则仍然遵循CLS
{
ulong x;
return a + b;
}
}
}
使用.Net自带的 ILDisassembler.exe 察看的IL:
extends [mscorlib]System.Object
{
.method public hidebysig instance int32
Add(int32 a,
int32 b) cil managed
{
// Code size 4 (0x4)
.maxstack 8
IL_0000: ldarg.1
IL_0001: ldarg.2
IL_0002: add
IL_0003: ret
} // end of method Test::Add
}
就是这三样东西组成的dll,让.net实现了远程处理的remoting,反射,序列化和跨平台,跨语言。关于跨平台的问题,我们在下一篇讨论。。嘿嘿!卖个关子
(二)
上一篇我们讲解了CIL,CTS,程序集等内容,并且留下了一个问题,就是.net如何跨平台,我们现在就来弄明白这个问题.由于篇幅有限,请大家参照第一篇的图.
按照流程图我们该讲解mscoree.dll了
mscoree.dll:公共对象运行库执行引擎.由他来寻找所有要加载的dll的位置并且加载他们.然后读取程序集中的元数据.所以他又被称为CLR的垫片.
CLR:公共语言运行库.前面我们已经启动了他的垫片,那么下来就要.net的灵魂人物出场了,他就是CLR,如同Java的虚拟机一样,.net缺少了他,就成了残废了,呵呵.在mscoree.dll加载了类型以后,CLR会以特定的机器CPU来加载对应的dll(mscorwk.dll或者mscorsrv.dll,这就是.net的性能为何很好的原因),然后CLR会在内存中为类型布局,将关联的CIL,运用JIT编译成特定平台的指令.嘿嘿,也许大家已经猜到了.net是如何跨平台的了吧.因为.net的dll实际是在运行时,才全部由各平台的JIT来编译成机器指令的.所以自然就可以跨平台喽.
CLR中的GC:垃圾回收器. 我们知道.net中一般情况是不需要手动释放资源的,做过C++的人都知道手动释放资源意味着什么:莫名其妙的错误,无尽的深渊....,在.net中所有的托管资源都有GC这个保洁员来给你打扫卫生.CLR中会创建"对象图"和标示对象的"代".
1.对象图说白了就是检查对象是否还可以在被引用.
2.代这个概念是用来帮助GC快速的来检查哪些对象没有被引用,在.net中有0,1,2三代.0是新创建的对象,1是执行了一次回收以后,存在引用的对象,2是执行了一次以上回收,还没有被清理的对象.GC总是从0代开始回收,如果内存已经够用,则不对1代操作,否则就检查一代是否存在没有引用的对象.执行回收,如果还不够,就清理2代.
我们来看看GC是如何工作的:
ABCDF被标示为 0代
我们现在回收0代的对象,因为BF没有引用,所以回收他们的内存,并且压缩托管堆.
现在 ACD被标示为 1代
如果再来一次回收如果ACD依然没有被回收,那么他们就被标示为2代.OK,现在我们明白了GC是如何来释放资源的了.
.net 进程: 一个.net进程中包含多个应用程序域,这就是.net平台操作系统独立性的关键特性,和完整的进程相比,应用程序域的CPU和内存都占用得比较少,因此CLR加载和卸载应用程序域(Appdomain)就快很多.
应用程序域(Appdomain):其中包含多个需要的Dll和上下文(Context),也就是说每个Appdomain都有mscorlib.dll等dll,下一篇中我们将介绍.net中常用的Dll.
.NET 框架与架构模式和设计模式详解系列(图解DotNet框架)之二System
前面我们学习了.net的运行引擎,知道了Appdomain要承载各种dll,那么我们就来学习一下最基本的system.dll,这个任何代码都要引用的大家伙.
还是老规矩,图解..嘿嘿上图了
看了图以后大家应该有一个初步的了解了吧,我们来一个一个的学习吧,大家都知道.net中分为值类型和引用类型,我们来看这两种类型的区别
object:这个类是.net所有类的父类,他有一些公用的方法,如ToString,Equals,GetHashCode等,这里用到GOF23中模板方法(Templete Method)这个设计模式,其实我们在开发中经常去用这个模式,这样就省去了其他的类重复来写这些方法.
ValueType:.net中所有的值类型都派生自这个类,而他自己却有派生自Object,很奇怪吧,实际上只是MS为了让.net完全的面向对象而已,没有什么,理解就好.
1.赋值问题:值类型和引用类型在内存中分别存在栈和堆上,他们在赋值的时候,值类型是复制,引用类型是本身(实际上是产生一个对堆上同一个对象的新引用).要说明的是包含引用类型的值类型,在赋值时也是Copy了引用,不要迷糊哦.
我们来写点代码说明这个问题吧.
{
int i = 10;
int j = i;
j = 20;
Console.Write(i);/**////结果i还是10,而没有变成20,说明赋值是复制
Test t1 = new Test();
t1.age = 10;
t1.name = "test";
Test t2 = new Test();
t2 = t1;
t1.age = 20;
t1.name = "Test2";
Console.Write(t2.age.ToString()+"&"+t2.name);/**////结果是"20&Test2",说明传递的是本身
}
2.作为参数传递的问题:值类型作为参数传递时如果按值传递那么就是Copy,如果按照引用传递那么就是本身,那么引用类型呢?我们来看段代码:
所以说按照引用传递引用类型,传递的是本身,而按照值类型,传递的是引用类型的引用
{
class Program
{
class Test
{
public string name; //为了方便就不封装了
public int age;
}
static void Main(string[] args)
{
Test t1 = new Test();
t1.age = 10;
t1.name = "test";
TestMethod(t1);
Console.Write(t1.age.ToString() + "&" + t1.name);/**////结果是"20&Test2",说明传递的是本身的引用,而一个引用重新赋值不会影响本身
TestMethod(ref t1);
Console.Write("\r\n"+t1.age.ToString() + "&" + t1.name);/**////结果是"0&null",说明传递的是本身.
Console.Read();
}
static void TestMethod(Test t)
{
t.age = 20;
t.name = "Test2";
t = new Test();//这里重新初始化
}
static void TestMethod(ref Test t) //看看按照引用传递会发生什么?
{
t.age = 20;
t.name = "Test2";
t = new Test();//这里重新初始化
}
Delegate:委托的基类,要注意delegate关键字,继承自MulticastDelegate,而MulticastDelegate继承自Delegate.委托类型维护的方法的地址列表,就保存在Delegate这个对象当中.这就是C#委托的秘密.这里用到了GOF23中Proxy(代理)这种设计模式,起到下述 3)保护代理的作用。
在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用P r o x y 模式。下面是一 些可以使用P r o x y 模式常见情况:
1) 远程代理(Remote Proxy )为一个对象在不同的地址空间提供局部代表。
2 )虚代理(Virtual Proxy )根据需要创建开销很大的对象。
3) 保护代理(Protection Proxy )控制对原始对象的访问。保护代理用于对象应该有不同 的访问权限的时候。4 )智能指引(Smart Reference )取代了简单的指针,它在访问对象时执行一些附加操作。
在这里Delegate作为RealSubject,而multicastDelegate作为Proxy,来保护Delegate中的一些方法.
图解DOTNET框架之三System.IO
在上一篇,我们讲解了System.dll,现在我们来看System.IO,要操作IO,就需要,确定储存媒介,打开,然后读取或者写入,那么在.Net中这三步是如何的呢?还是老规矩上图
1.打开:
Directory和File,前者用来操作文件夹,后者操作文件,它们和FileSystemInfo的两个子类的区别在于,他们是静态类,对文件或文件夹的操作是弱类型的。而FileSystemInfo的两个子类是实例类,它们是强类型的。
2.储存媒介:
抽象Stream基类,提供了对储存媒介的同步或异步的访问,他把数据流表示为原始的字节流,他的三个子类提供了,基于文件的Filestream,基于内存的MenoryStream。这里用到了GOF23中Decorator(装饰),动态地给一个对象添加一些额外的职责。
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 处理那些可以撤消的职责。
- 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
上图中的Component在.net里就是Stream, ,COncreateDecorator:FileStream,MemoryStream,BufferedStream 进行装饰,生成各具不同功能的对象,不同格式的流。
3.读取或写入:读取(写入)有三种方式,StreamReader(Writer):以字节流读取(写入),StringReader(Writer):以字符串流读取(写入),BinaryReader(Writer):以2进制流读取(写入)
通过这三个层次的类,就可以完成IO的操作,简单的就是用File打开一个文件,它会返回一个Stream,然后用相应的读写器去读取或者写入.
图解DOTNET框架之三System.Data
ADO.net类库有两种完全不同的方式来实现数据访问:连接式和断开式.ADO.NET没有提供单一的方式和不同的数据库进行通信,而是提供了多种数据提供器,这样的好处是可以将某种类型数据库的特性发挥出来.我们来看看ADO.NET中的核心类。
IDbConnection:定义配置某个数据库连接的一些成员。如连接字符串,超时,连接状态等。
IDbCommand:定义命令以及所执行的操作。如ComandText,ExecuteReader,ExecuteNonquery,Parameter等。
IDbDataParameter:他继承了IDataParmeter并扩展了他,定义了DbType,Direction,ParameterName,Value等。
IDbDataAdapter:继承自IdataAdapter,IdataAdapter中主要来完成数据的映射,然后IDbDataAdapter扩展了IdataAdapter,加入了,Delete,select,Update,Insert这四个IDbCommand命令。
IDataReader:IDataReader继承了IDataRecord并对其扩展,Idatarecord用来定义读取强类型化的数据,Getint16,GetDateTime之类,Idatareader加入了读取对象的常用行为,Next,Depth等。
图中Dbconnection,DbCommand等抽象类分别继承了上述的接口,并实现了一些任何数据库都公用的方法而已,这里不再赘述。
他们之间的协作关系如图:
在这里就要讲到题目所示的架构模式中的数据源架构模式和领域逻辑模式,MS在这里用了什么模式呢,首先我们来了解一下数据源都有哪几种架构模式:
1.表数据入口:一个表实例处理数据库表中的所有行.(Adapter中承载了一张表,他就代表数据库,可以对数据进行填删改查等操作)
2.行数据入口:访问表中单条记录地对象,一行一个实例.(DataReader,这里的行数据只能读取,而不能修改.每一次Reader.Next(),就会返回一个数据行.)
3.数据影射器(ORM):.net中没有这种模式,ORM本身就很复杂,这里就不讲解了.
在.net中有表数据入口和行数据入口,这两种模式的实现是为了满足一些应用,行数据可以更好的面向对象,表数据操作起来直观.
领域逻辑模式,也就是所谓的业务罗基层,它有以下几种架构模式:
1.事物脚本:也就是使用过程来实现业务逻辑.老ASP的写法,就是这种过程性的编程.
2.领域模型:合并了行为和数据的对象模型,比如你建立了一个Person类,里面包括他的名字,这就是数据,然后你的业务逻辑是给他改名字,就会做一个改名字的方法.这就是简单的领域模型.
3.表模块:处理数据库表或视图中的所有业务逻辑的一个实例,他通常需要平台的支持,而.NET中的DataSet就是这种模式的应用。
我们可以用Dataset来完成我们的业务逻辑,一个很方便的应用.