C# 阅读笔记(一)
1.托管代码
首先我们需要了解一下.NET的编译机制,在.NET框架下,编译分为两个步骤完成,第一阶段编译和JIT编译,在第一阶段编译过程中,任何语言都会被编译成中间语言(IL)代码,即托管代码。
托管代码实际上就是中间语言(IL)代码,代码编写完以后进行编译,此时C#编译器将代码编译成中间语言,而不是直接在计算机上运行的机器码,程序集(Assembly)的文件负责封装中间语言。
托管代码在公共语言运行库(CLR)中运行,这个运行库给运行代码提供各种服务,当代码中的某些方法被调用的时候,CLR把具体的方法编译成适合本地计算机运行的机器码,并且将编译好的机器码缓存起来,以备下次调用时使用,这个过程就叫即时编译,亦叫JIT编译(just in time)
程序在编译成IL代码以后,实际上是被托管在CLR上,随着程序集的运行,CLR会持续的提供各种服务,如内存管理,安全管理,线程管理等。
2,非托管代码
非托管代码是指由高级语言直接编译成的目标计算机的机器码,机器码只能运行在编译出这些代码的计算机上,非托管代码不能享受公共语言运行库所提供的各种服务,如内存管理,安全管理,线程管理等,如果它需要这些服务,就必须显示的调用操作系统的接口,通常非托管代码调用Windows SDK所提供的API来实现内存管理。也可以通过调用COM接口来获取操作系统的服务。
3.IL语言如何在.NET下运行
在.NET框架中,公共语言基础结构使用公共语言规范来绑定不同的语言,不同的语言均需实现公共类型系统(CTS)在公共语言规范中定义的部分,因此公共语言基础结构允许不同的语言使用
.NET框架,在.NET框架中,所有的语言在编译的过程中都将被转化为一种通用语言,即中间语言,它是一种介于高级语言和汇编语言之间的伪汇编语言。从理论上讲,IL将消除业界不同语言之间的竞争。
元数据
理解反射,需要先理解元数据,什么吗是元数据,元数据是不同语言采用其支持.NET平台的编译器经过编译后的产物,其中包括IL代码和元数据,组件负责对它们进行封装。元数据集中了组件执行的指令与接口描述。元数据包含组件的逻辑接口描述,提供了组件的版本信息、公钥、私钥,以及它引用了哪些程序集,其中还有它所包含类型的类型信息以及类型中所包含的成员。支持丰富的运行时查询,调用和生成。
CLR - 释义
CLR(公共语言运行库)和Java虚拟机一样也是一个运行时环境,它负责资源管理(内存分配和垃圾收集),并保证应用和底层操作系统之间必要的分离。
为了提高平台的可靠性,以及为了达到面向事务的电子商务应用所要求的稳定性级别,CLR还要负责其他一些任务,比如监视程序的运行。按照.NET的说法,在CLR监视之下运行的程序属于“受管理的”(managed)代码,而不在CLR之下、直接在裸机上运行的应用或者组件属于“非受管理的”(unmanaged)的代码。
CLR将监视形形色色的常见编程错误,许多年来这些错误一直是软件故障的主要根源,其中包括:访问数组元素越界,访问未分配的内存空间,由于数据体积过大而导致的内存溢出,等等。
然而,这种对受管理代码的运行监视是有代价的。虽然当前还不可能精确地得到监视程序运行所需要的开销,但从当前beta测试版的性能表现来看,正如Microsoft所承认的那样,我们可以预料由它导致的性能降低程度至少达到10%。当然,如果监视程序运行能够将稳定性和可用性提高到一个新的档次,我们可以怀疑10%的性能降低是否还可以称为一件坏事……
在处理器性能改善方面,摩尔定律已经一再被证明是正确的。既然如此,我们要得到一台性能增加了10%的服务器要等待多长时间呢
4.CTS(Common Type System)通用类型系统
定义了可以在中间语言中使用的预定义数据类型,所有面向.NET Framework的语言都可以产生最终机遇这些类型的编译代码,CTS的层次结构反应了中间语言单一继承的面向对象的方法。CTS不但定义了基础的数据类型,还定义了一个内容丰富的类型层次结构,在这些位置上,代码允许定义自己的类型。
5.Array与ArrayList的的相似与区别
相同点:
1.Array与ArrayList所创建的对象均放在托管堆中。
2.Array与ArrayList都具有索引,既可以通过索引来获取或修改任意项。
3.Array与ArrayList都能对自身进行枚举。
区别:
1.命名空间不同:Array位于System命名空间内,但ArrayList位于System.collections命名空间内。
2.变量声明不同:Array类型的变量在声明的时候必须进行实例化(初始化数组的大小),而ArrayList可以只是先声明。
3.设置下限不同:可以设置Array的下限,但ArrayList的下限始终为零。
4.设置元素不同:ArrayList提供添加、插入或移除某一范围元素的方法,在Array中,只能一次获取或设置一个元素的值。
5.拥有的维度不同:Array可以是多维的,但ArrayList永远是一维的。
6.存储的对象不同:Array只能存储同构对象,ArrayList允许存储异构对象。
7.在CLR托管堆中存放的方式不同:Array始终是连续存放的,而ArrayList的存放不一定连续。
8.初始化大小不同:Array对象在初始化时必须指定大小,且以后大小固定,而ArrayList的大小可以动态指定,也就是说对象的空间可以任意的增加。
9.设置项不同:Array不能够随意添加和删除其中的项,而ArrayList可以在任意位置插入和删除项。
注:ArrayList有个致命的特点:即在ArrayLst内存区域的对象被处理以后,它所占据的内存空间并不会被回收,因此导致在ArrayList添加新的对象的时候需要不断的增加缓冲区,从而导致ArrayList的长度就会不断的增长。
6.System.Object
支持.NET层次结构中的所有类,并且为其派生类提供低级别的服务,这是 .NET Framework 中所有类的最终基类;它是类型层次结构的根。语言通常不要求类声明从 Object 的继承,因为继承是隐式的。因为 .NET Framework 中的所有类均从 Object 派生,所以 Object 类中定义的每个方法可用于系统中的所有对象。派生类可以而且确实重写这些方法中的某些,其中包括:
Equals— 支持对象间的比较,确定指定的 Object 是否等于当前的 Object。
Finalize — 在自动回收对象之前执行清理操作。
GetHashCode — 生成一个与对象的值相对应的数字以支持哈希表的使用。
ToString — 生成描述类的实例的可读文本字符串。
7.System.ValueType
它是所有值类型的基类,所有值类型均直接或间接继承自它,它本身继承自System.Object,因此它本身是一个引用类型,特别的是继承自它的类型均为值类型。
8.程序集(Assembly)
程序集是.NET组建的物理形式(如解决方案里面的.dll文件),包括IL代码(Intermediate Language)和元数据(Metadata)。是组建复用,实施安全策略与版本策略的最小单位,在.NET中已.dll和.exe文件的的形式存在。
关于IL代码和JIT编译的概念前面的知识点中均进行过解释,这里就不再赘述,我这里想举一个例子谈谈JIT编译的机制。
JIT编译:完成从虚拟逻辑语义到物理细节的转换。(产生本地代码,并保存在当前进程的内存中)
1.运行时编译,本地代码存储在当前进程的内存中。
2.以方法为单位整体编译。
3.按需编译,且仅编译一次。
class Test
{
static void Main()
{
int data = 10;
if (data < 0)
{
Process();
}
}
public static void Process()
{
Console.WriteLine("HelloWorld");
}
}
此段代码经过第一次C#编译器编译成IL代码和元数据以后,现在还无法执行,需要执行必须经过JIT编译,当函数执行Main()时,此时Main()函数还没有经过JIT编译,当执行到if语句的时候,由于条件为假,并不需要执行Process()函数,由于JIT编译器是按需编译的,因此JIT编译器并不会对Process()函数进行编译。现在我们修改条件,将data < 0改为data>0,且增加一次Process()的执行,代码如下:
class Test
{
static void Main()
{
int data = 10;
if (data > 0)
{
Process();
}
Process();
}
public static void Process()
{
Console.WriteLine("HelloWorld");
}
}
当执行到if语句的时候,由于条件为真,需要执行Process()函数,此时JIT编译器就会对该函数进行编译,后面还需要执行一次Process()函数,此时JIT编译器已将第一次的编译结果保存在当前进程的内存中,只需要调用即可。JIT编译发生在运行时,但是具体到方法,它是在方法执行之前,来进行JIT编译的。整个方法编译完,然后开始执行。
引用类型对象存储与托管堆上,值类型存储于栈上,静态字段存储在全局数据区,常量存储在代码段中(实际是经JIT编译后形成的本地代码所占据的内存中)。
内存分区:实际上,系统为当前进程所分配的内存大致被分为四片区域:栈,堆,全局数据区,本地代码所占据内存区。
组件在内部的封装性及外部接口的稳定性方面有高度的体现。组件就是程序集(Assembly)即dll文件,只有外部接口稳定了,才具有复有价值。微软的.NET Framework是世界上复用性最高的组件,它要供世界上几百万程序员的在很长一段时间不断的去复用,这个时候就需要特别注意外部接口的稳定性。组件封装的目的就是为了使外部接口稳定下来,让变化都发生在内部。
组件与类的比较:
类的粒度要比组件小,一个组件可以包含1个类或多个类。通常来讲,组件在类的基础上添加了如下构造和功能:
1. 元数据,从而支持组件面向外部接口的自描述
2. 运行时,或者讲支持平台,支持组件生命周期、版本管理等
3. 面向接口,使用接口来支持黑盒复用
组件并非只有属性和方法,还可以有比如:常量、事件、以及特性等。 总体而言,类,一般是从源代码级别来谈,封装、继承、多态。 而组件,一般是从二进制级别来谈:接口、元数据、运行时。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架