记录点滴收获,汇聚知识乐园

脚步无法到达的地方,目光可以到达;目光无法到达的地方,梦想可以到达

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

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 - 主要功能

    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. 面向接口,使用接口来支持黑盒复用

组件并非只有属性和方法,还可以有比如:常量、事件、以及特性等。 总体而言,类,一般是从源代码级别来谈,封装、继承、多态。 而组件,一般是从二进制级别来谈:接口、元数据、运行时。

posted on   guowenhui  阅读(288)  评论(0编辑  收藏  举报

编辑推荐:
· 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 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示