微软公司是全球最大的电脑软件提供商,为了占据开发者市场,进而在2002年推出了Visual Studio(简称VS,是微软提供给开发者的工具集) .NET 1.0版本的开发者平台。而为了吸引更多的开发者涌入平台,微软还在2002年宣布推出一个特性强大并且与.NET平台无缝集成的编程语言,即C# 1.0正式版。
只要是.NET支持的编程语言,开发者就可以通过.NET平台提供的工具服务和框架支持便捷的开发应用程序。
C#就是为宣传.NET而创立的,它直接集成于Visual Studio .NET中,VB也在.NET 1.0发布后对其进行支持, 所以这两门语言与.NET平台耦合度很高,并且.NET上的技术大多都是以C#编程语言为示例,所以经常就.NET和C#混为一谈(实质上它们是相辅相成的两个概念)。
.net作为一个开发者平台,它不仅仅是包含开发环境、技术框架、社区论坛、服务支持等,它还强调了平台的跨语言、跨平台编程的两个特性。
跨语言:即只要是面向.NET平台的编程语言((C#、Visual Basic、C++/CLI、Eiffel、F#、IronPython、IronRuby、PowerBuilder、Visual COBOL 以及 Windows PowerShell)),用其中一种语言编写的类型可以无缝地用在另一种语言编写的应用程序中的互操作性。
跨平台:一次编译,不需要任何代码修改,应用程序就可以运行在任意有.NET框架实现的平台上,即代码不依赖于操作系统,也不依赖硬件环境。
在面向.NET开发中,编写跨语言组件时所遵循的那些共性,那些规范就叫做 Common Langrage Specification共同语言基础设施,简称 CLS
当你需要设计面向.Net的语言时所需要遵循一个体系(.Net平台下的语言都支持的一个体系)这个体系就是CTS(Common Type System 公共类型系统)
CTS定义的一个非常重要的规则,就是类与类之间只能单继承,System.Object类是所有类型的根,任何类都是显式或隐式的继承于System.Object。
微软已经将CTS和.NET的一些其它组件,提交给ECMA以成为公开的标准,最后形成的标准称为CLI(Common Language Infrastructure)公共语言基础结构。
.NET提供了一个垃圾回收器(GC)来完成这部分工作,当你创建类型的时候,它会自动给你分配所需要的这部分内存空间。就相当于,有一个专门的软件或进程,它会读取你的代码,然后当你执行这行代码的时候,它帮你做了内存分配工作。 这部分本该你做的工作,它帮你做了,这就是“托管”的概念。
因此,C#被称之为托管语言。C#编写的代码也就称之为托管代码,C#生成的模块称之为托管模块等。
那些脱离了.NET提供的诸如垃圾回收器这样的环境管制,就是对应的非托管了。
我们编写的程序有的模块是由托管代码编写,有的模块则调用了非托管代码。在.NET Framework中也有一套基于此操作系统SEH的异常机制,理想的机制设定下我们可以直接通过catch(e)或catch来捕获指定的异常和框架设计人员允许我们捕获的异常。
.NET不仅提供了自动内存管理的支持,他还提供了一些列的如类型安全、应用程序域、异常机制等支持,这些 都被统称为CLR公共语言运行库。
在我们执行托管代码之前,总会先运行这些运行库代码,通过运行库的代码调用,从而构成了一个用来支持托管程序的运行环境,进而完成诸如不需要开发人员手动管理内存,一套代码即可在各大平台跑的这样的操作。
这套环境及体系之完善,以至于就像一个小型的系统一样,所以通常形象的称CLR为".NET虚拟机"。那么,如果以进程为最低端,进程的上面就是.NET虚拟机(CLR),而虚拟机的上面才是我们的托管代码。换句话说,托管程序实际上是寄宿于.NET虚拟机中。
那么相对应的,容纳.NET虚拟机的进程就是CLR宿主进程了,该程序称之为运行时主机。
一个demo.exe的可执行程序,运行过程如下:
C#源码被编译成程序集,程序集内主要是由一些元数据表和IL代码构成,我们双击执行该exe,Windows加载器将该exe(PE格式文件)给映射到虚拟内存中,程序集的相关信息都会被加载至内存中,并查看PE文件的入口点(EntryPoint)并跳转至指定的mscoree.dll中的_CorExeMain函数,该函数会执行一系列相关dll来构造CLR环境,当CLR预热后调用该程序集的入口方法Main(),接下来由CLR来执行托管代码(IL代码)。
JIT编译
在CLR下有一个用来将IL代码转换成机器码的引擎,称为Just In Time Compiler,简称JIT,CLR总是先将IL代码按需通过该引擎编译成机器指令再让CPU执行,在这期间CLR会验证代码和元数据是否类型安全(在对象上只调用正确定义的操作、标识与声称的要求一致、对类型的引用严格符合所引用的类型)。
被编译过的代码无需JIT再次编译,而被编译好的机器指令是被存在内存当中,当程序关闭后再打开仍要重新JIT编译。
AOT编译
每次在对代码编译时都需要一个预热的操作,它需要一个运行时环境来支持,这之间还是有消耗的。而与即时编译所对应的,就是提前编译了,英文为Ahead of Time Compilation,简称AOT,也称之为静态编译。
在.NET中,使用Ngen.exe或者开源的.NET Native可以提前将代码编译成本机指令。
Ngen是将IL代码提前给全部编译成本机代码并安装在本机的本机映像缓存中,故而可以减少程序因JIT预热的时间,但同样的也会有很多注意事项,比如因JIT的丧失而带来的一些特性就没有了,如类型验证。
Ngen仅是尽可能代码提前编译,程序的运行仍需要完整的CLR来支持。
程序集的加载方式
对于自身程序集内定义的类型,我们可以直接从自身程序集中的元数据中获取,对于在其它程序集中定义的类型,CLR会通过一组规则来在磁盘中找到该程序集并加载在内存。
签名
可以通过sn.exe或VS对项目右键属性在签名选项卡中采取RSA算法对程序集进行数字签名(加密:公钥加密,私钥解密。签名:私钥签名,公钥验证签名),会将构成程序集的所有文件通过哈希算法生成哈希值,然后通过非对称加密算法用私钥签名,最后公布公钥生成一串token,最终将生成一个由程序集名称、版本号、语言文化、公钥组成的唯一标识,它相当于一个强化的名称,即强名称程序集。
日常在VS中的项目默认都没有被签名,所以就是弱名称程序集。强名称程序集是具有唯一标识性的程序集,并且可以通过对比哈希值来比较程序集是否被篡改。
CLR查找程序集的时候,会先判断该程序集是否被强签名,如果强签名了那么就会去共享程序集的存储位置(后文的GAC)去找,如果没找到或者该程序集没有被强签名,那么就从该程序集的同一目录下去寻找。
程序集之间的依赖顺序决定了编译顺序,所以在设计项目间的分层划分时不仅要体现出层级职责,还要考虑到依赖顺序。代码存放在哪个项目要有讲究,不允许出现互相引用的情况,比如A项目中的代码引用B,B项目中的代码又引用A。
共享程序集GAC
类似于mscorlib.dll、System.dll这样的FCL类库被引用的如此频繁,它已经是我们.NET编程中必不可少的一部分,几尽每个项目都会引用,为了不再每次使用的时候都复制一份,所以计算机上有一个位置专门存储这些我们都会用到的程序集,叫做全局程序集缓存(Global Assembly Cache,GAC),这个位置一般位于C:\Windows\Microsoft.NET\assembly和3.5之前版本的C:\Windows\assembly。
规定在GAC中只能存在强名称程序集,每当CLR要加载强名称程序集时,会先通过标识去GAC中查找。
传统非托管程序是直接承载在Windows进程中,托管程序是承载在.NET虚拟机CLR上的,而在CLR中管控的这部分资源中,被分成了一个个逻辑上的分区,这个逻辑分区被称为应用程序域,是.NET Framework中定义的一个概念。
跨边界访问
事实上,在开发中我们还应该注意跨域访问对象的操作(即在A域中的程序集代码直接调用B域中的对象)是与平常编程中有所不同的,一个域中的应用程序不能直接访问另一个域中的代码和数据,对于这样的在进程内跨域访问操作分两类。
一是按引用封送,需要继承System.MarshalByRefObject,传递的是该对象的代理引用,与源域有相同的生命周期。
二是按值封送,需要被[Serializable]标记,是通过序列化传递的副本,副本与源域的对象无关。
无论哪种方式都涉及到两个域直接的封送、解封,所以跨域访问调用不适用于过高频率。
七、内存
程序运行的过程就是电脑不断通过CPU进行计算的过程,这个过程需要读取并产生运算的数据,为此我们需要一个拥有足够容量能够快速与CPU交互的存储容器,这就是内存了。
内存区域分为堆栈和托管堆
堆和堆栈就内存而言只不过是地址范围的区别。
不过堆栈的数据结构和其存储定义让其在时间和空间上都紧密的存储,这样能带来更高的内存密度,能在CPU缓存和分页系统表现的更好。故访问堆栈的速度总体更快点。
线程堆栈
操作系统会为每条线程分配一定的空间,Windwos为1M,这称之为线程堆栈。
在CLR中的栈主要用来执行线程方法时,保存临时的局部变量和函数所需的参数及返回的值等,在栈上的成员不受GC管理器的控制,它们由操作系统负责分配,当线程走出方法后,该栈上成员采用后进先出的顺序由操作系统负责释放,执行效率高。
而托管堆则没有固定容量限制,它取决于操作系统允许进程分配的内存大小和程序本身对内存的使用情况,托管堆主要用来存放对象实例,不需要我们人工去分配和释放,其由GC管理器托管。
值类型本身就是固定的内存占用大小,所以将值类型放在内存连续分配的栈中。
对于引用类型这种无法预知的容易产生内存碎片的动态内存,我们把它放到托管堆中存储。
八、安全性
基于角色的安全性
基于角色的安全机制作为传统的访问控制
代码访问安全性(CAS、安全透明模型)
用来帮助限制代码对受保护资源和操作的访问权限。
.NET Framework就是.NET 技术框架组成在Windows系统下的具体的实现,和Windows系统高度耦合;
.NET Core是.NET 技术框架组成在Windows.macOS.Linux系统下的具体的实现;
.NET Standard是正式的 .NET API (.NET 的应用程序编程接口)规范。
附:官方文档,部分常用学习总链接:
asp.net指南:https://docs.microsoft.com/zh-cn/aspnet/#pivot=core
Visual Studio IDE 指南:https://docs.microsoft.com/zh-cn/visualstudio/ide/
C# 指南: https://docs.microsoft.com/zh-cn/dotnet/csharp/
.NET指南:https://docs.microsoft.com/zh-cn/dotnet/standard/
微软开发文档:https://docs.microsoft.com/zh-cn/