C#总结

1   基本概念

1.1 .NetFramework

CLR和类库组成。CIL(中间语言)。

 
   

 

 

 

 

 

 

 

 

 
   

 

 

 

 

 

 

 

CIL——基于.NET的语言,都编译成CIL。然后运行于CLR中,称之为托管。

CTS——如何定义,才能被CLR承载。包括类,接口,结构,枚举,委托,数据类型。

CLS——是CTS的一个子集。支持.NET编译的特征集。以生成可由CLR承载的代码,以使其他基于.net的语言可通过统一的方式访问。

 

 

 

1.2 .NET程序执行原理

C#源码--程序集(IL和元数据)--CLR

 

现在我们已经有了一个demo.exe的可执行程序,它是如何被我们运行的?

 

C#源码被编译成程序集,程序集内主要是由一些元数据表和IL代码构成,我们双击执行该exe,Windows加载器将该exe(PE格式文件)给映射到虚拟内存中,程序集的相关信息都会被加载至内存中,并查看PE文件的入口点(EntryPoint)并跳转至指定的mscoree.dll中的_CorExeMain函数,该函数会执行一系列相关dll来构造CLR环境,当CLR预热后调用该程序集的入口方法Main(),接下来由CLR来执行托管代码(IL代码)。

 

1.3 托管

托管: 由CLR执行的代码。

非托管:由操作系统直接执行的代码。

 

托管指被CLR管理。托管资源主要是指托管堆上分配的内存资源, 其由.NET运行时在适当的时候调用垃圾回收器进行回收。非托管资源指的是.NET不知道如何回收的资源,最常见的一类非托管资源如文件、窗口、网络连接、数据库连接、画刷、图标等。

非托管代码必须提供自己的垃圾回收,类型检查,安全支持等。对于这类资源虽然垃圾回收器( GC)可以跟踪封装非托管资源的对象的生存期,但它不了解具体如何清理这些资源。常见的非托管源有:

ApplicationContext,Brush,Component,ComponentDesigner,Container,Context,Cursor,

FileStream,Font,Icon,Image,Matrix,Object,OdbcDataReader,OleDBDataReader,Pen,

Regex,Socket,StreamWriter,Timer,Tooltip 等

 

1.1 托管执行过程

包括以下几个步骤:
1. 选择编译器
2. 将代码编译为 MSIL
3. 将 MSIL 编译为本机代码
4. 运行代码

1.1.1 选择编译器

即选择对应语言的编译器,来进行语言级别的处理,如语法检查;

1.1.2 将代码编译为 MSIL

编译器将源代码转换为一组独立于CPU且可转换为本地代码的指令,即MSIL。它包括有关加载、存储、初始化和调用对象方法的指令,以及有关算术和逻辑运算、控制流、直接内存访问、异常处理和其他操作的指令。当编译器生成 MSIL 时,它还生成元数据,其描述代码中的类型,包括每种类型的定义、每种类型的成员的签名、代码引用的成员以及运行时在执行时间使用的其他数据。
正是文件中元数据的存在以及 MSIL 使代码的自描述性,使得我们不需要类型库或接口定义语言 (IDL)。因为运行时在执行期间会根据需要从文件中查找并提取元数据。

1.1.3 将 MSIL 编译为本机代码

.NET framework提供了两种转换方式:JIT(实时编译器)和NGen.exe(本机映像生成器)
1. JIT(实时编译器): 在加载和执行程序集的内容时,JIT 编译器将在应用程序运行时按需将 MSIL 转换为本机代码
2. NGen.exe:因为JIT是在运行时将MSIL转换为本机代码,而这会导致性能的下降;并且,JIT生成的代码会绑定到触发编译的进程上,从而导致这些代码无法在多个进程之间共享。 因此,NGen.exe应运而生。

NGen.exe(本机映像生成器)有着JIT无法替代的优势:
- 允许生成的代码跨应用程序的多个调用或跨共享一组程序集的多个进程进行共享
- 它一次编译整个程序集,而不是一次编译一种方法:虽然首次编译较慢,但运行时却会快一些,有助于提升用户体验
- 提供代码验证机制:检查 MSIL 和元数据,以找出代码是否为类型安全,其有助于将对象隔离开来,保护这些对象免受有意或无意的损坏

1.1.4 运行代码

CLR为托管代码的执行,提供基础服务:在执行期间,托管代码接收服务,如垃圾收集、安全性、与非托管代码的互操作性、跨语言调试支持以及增强的部署和版本控制支持。

 

1.2 垃圾回收

GC自动释放托管堆上的资源。

首先,GC并不是能释放所有的资源。它不能自动释放非托管资源。
  第二,GC并不是实时性的,这将会造成系统性能上的瓶颈和不确定性。

第三,GC是按代回收的,上一轮扫描时没有释放的,存活的时间会更长。

 

1.2.1 标记压缩

阶段1: Mark-Sweep 标记清除阶段 
先假设heap中所有对象都可以回收,然后找出不能回收的对象,给这些对象打上标记,最后heap中没有打标记的对象都是可以被回收的 
阶段2: Compact 压缩阶段 
对象回收之后heap内存空间变得不连续,在heap中移动这些对象,使他们重新从heap基地址开始连续排列,类似于磁盘空间的碎片整理 

Heap内存经过回收、压缩之后,可以继续采用前面的heap内存分配方法,即仅用一个指针记录heap分配的起始地址就可以 
主要处理步骤:将线程挂起=>确定roots=>创建reachable objectsgraph=>对象回收=>heap压缩=>指针修复 

1.2.2 Generational 分代算法 

程序可能使用几百M、几G的内存,对这样的内存区域进行GC操作成本很高,分代算法具备一定统计学基础,对GC的性能改善效果比较明显 
将对象按照生命周期分成新的、老的,根据统计分布规律所反映的结果,可以对新、老区域采用不同的回收策略和算法,加强对新区域的回收处理力度,争取在较短时间间隔、较小的内存区域内,以较低成本将执行路径上大量新近抛弃不再使用的局部对象及时回收掉 
分代算法的假设前提条件: 
1、大量新创建的对象生命周期都比较短,而较老的对象生命周期会更长 
2、对部分内存进行回收比基于全部内存的回收操作要快 
3、新创建的对象之间关联程度通常较强。heap分配的对象是连续的,关联度较强有利于提高CPU cache的命中率 
.NET将heap分成3个代龄区域: Gen 0、Gen 1、Gen 2 

Heap分为3个代龄区域,相应的GC有3种方式: # Gen 0 collections, # Gen 1 collections, #Gen 2 collections。如果Gen 0 heap内存达到阀值,则触发0代GC,0代GC后Gen 0中幸存的对象进入Gen1。如果Gen 1的内存达到阀值,则进行1代GC,1代GC将Gen 0 heap和Gen 1 heap一起进行回收,幸存的对象进入Gen2。2代GC将Gen 0 heap、Gen 1 heap和Gen 2 heap一起回收 
Gen 0和Gen 1比较小,这两个代龄加起来总是保持在16M左右;Gen2的大小由应用程序确定,可能达到几G,因此0代和1代GC的成本非常低,2代GC称为fullGC,通常成本很高。粗略的计算0代和1代GC应当能在几毫秒到几十毫秒之间完成,Gen 2 heap比较大时fullGC可能需要花费几秒时间。大致上来讲.NET应用运行期间2代、1代和0代GC的频率应当大致为1:10:100。

 

1.2.3 Finalization Queue和Freachable Queue

这两个队列和.net对象所提供的Finalize方法有关。这两个队列并不用于存储真正的对象,而是存储一组指向对象的指针。当程序中使用了new操作符在Managed Heap上分配空间时,GC会对其进行分析,如果该对象含有Finalize方法则在Finalization Queue中添加一个指向该对象的指针。在GC被启动以后,经过Mark阶段分辨出哪些是垃圾。再在垃圾中搜索,如果发现垃圾中有被Finalization Queue中的指针所指向的对象,则将这个对象从垃圾中分离出来,并将指向它的指针移动到Freachable Queue中。这个过程被称为是对象的复生(Resurrection),本来死去的对象就这样被救活了。为什么要救活它呢?因为这个对象的Finalize方法还没有被执行,所以不能让它死去。Freachable Queue平时不做什么事,但是一旦里面被添加了指针之后,它就会去触发所指对象的Finalize方法执行,之后将这个指针从队列中剔除,这是对象就可以安静的死去了。.net framework的System.GC类提供了控制Finalize的两个方法,ReRegisterForFinalize和SuppressFinalize。前者是请求系统完成对象的Finalize方法,后者是请求系统不要完成对象的Finalize方法。ReRegisterForFinalize方法其实就是将指向对象的指针重新添加到Finalization Queue中。这就出现了一个很有趣的现象,因为在Finalization Queue中的对象可以复生,如果在对象的Finalize方法中调用ReRegisterForFinalize方法,这样就形成了一个在堆上永远不会死去的对象,像凤凰涅槃一样每次死的时候都可以复生。


  GC并不是实时性的,这会造成系统性能上的瓶颈和不确定性。所以有了IDisposable接口,IDisposable接口定义了Dispose方法,这个方法用来供程序员显式调用以释放非托管资源。使用using语句可以简化资源管理,自动调用Dispose释放资源。

 

The garbage collector in the common language runtime supports object aging using generations

Objects created more recently are part of newer generations, and have lower generation numbers than objects created earlier in the application life cycle. 

Objects in the most recent generation are in generation 0. This implementation of the garbage collector supports three generations of objects, generations 0, 1, and 2

每代都有自己的堆,假如0代的堆满了,就会触发GC,然后把依然有引用的对象升级,放到1代对象。最后压缩堆,把剩余的堆空间合并到一块。1代对象也是如此操作。但到了2代,就处理不同了。2代的堆可能是大对象堆,它的压缩代价过于高昂,所以只是合并相邻的空间。

 

Garbage collection happens automatically when a request for memory cannot be satisfied using available free memory

 GC发生的时机,就是相应的堆达到了阈值,因为堆也有大小限制,并不是无限的。尽管2代堆或者大对象堆满的时候,通过增加新的内存段来满足内存分配,如果没有可用的内存,这时就会报内存溢出。

 

一般情况下,我们并不需要调用GC.Collect()来触发垃圾回收。因为,让垃圾回收期自动去确定垃圾回收的时机往往会拥有更好的性能(在垃圾回收器执行之前,它会挂起当前正在运行的所有线程,以方便它检测各种对象之间的关联关系,从而分析出需要回收的垃圾)。
然而,自动的垃圾回收时机有时候无法满足我们的要求:比如大数据量或大图的载入。

不过,事物具有两面性。鉴于GC执行之前会挂起线程,故我们不应该频繁地调用GC.Collect()。否则可能会造成性能下降。
另外,关于GC.Collect()的使用,不在此赘述了。提醒一点的是,此方法的重载GC.Collect(int generation),参数表示此次需要强制回收到第几代的对象。

1.3 Using

using 关键字有两个主要用途:
  (一).作为指令,用于为命名空间创建别名或导入其他命名空间中定义的类型。
  (二).作为语句,用于定义一个范围,在此范围的末尾将释放对象。
 
using指令
    ①允许在命名空间中使用类型,这样,您就不必在该命名空间中限定某个类型的使用:
             using System.Text;
             using PC.Company;
     ②为命名空间或类型创建别名。
               using MyCompany = PC.Company;       //命名空间的别名。
               using Project = PC.Company.Project; //类型的别名
    using引入命名空间,并不等于编译器编译时加载该命名空间所在的程序集,程序集的加载决定于程序中对该程序集是否存在调用操作,如果代码中不存在任何调用操作则编译器将不会加载using引入命名空间所在程序集。因此,在源文件开头,引入多个命名空间,并非加载多个程序集,不会造成“过度引用”的弊端。
    创建别名的另一个重要的原因在于同一文件中引入的不同命名空间中包括了相同名称的类型,如SharpMap.Geometries.Point与System.Drawing.Point。为了避免出现名称冲突,可以通过设定别名来解决:
    using SGPoint = SharpMap.Geometries.Point;
    using SDPoint = System.Drawing.Point;
     尽管我们可以通过类型全名称来加以区分,但是这显然不是最佳的解决方案。用using指令创建别名,有效的解决了这种可能的命名冲突,才是最佳的解决方案。
 
using语句
    using 语句允许程序员指定使用资源的对象应当何时释放资源。using 语句中使用的对象必须实现 IDisposable 接口。此接口提供了 Dispose 方法,该方法将释放此对象的资源。
    ①可以在 using 语句之中声明对象。
       Font font2 = new Font("Arial", 10.0f);
      using (font2)
      {
          // use font2
      }
    ②可以在 using 语句之前声明对象。
       using (Font font2 = new Font("Arial", 10.0f))
      {
          // use font2
      }
    ③可以有多个对象与 using 语句一起使用,但是必须在 using 语句内部声明这些对象。
        using (Font font3=new Font("Arial",10.0f), font4=new Font("Arial",10.0f))
      {
          // Use font3 and font4.
      }
 
使用规则
    ①using只能用于实现了IDisposable接口的类型,禁止为不支持IDisposable接口的类型使用using语句,否则会出现编译错误;
   ②using语句适用于清理单个非托管资源的情况,而多个非托管对象的清理最好以try-finnaly来实现,因为嵌套的using语句可能存在隐藏的Bug。内层using块引发异常时,将不能释放外层using块的对象资源;
   ③using语句支持初始化多个变量,但前提是这些变量的类型必须相同,例如:
        using(Pen p1 = new Pen(Brushes.Black), p2 = new Pen(Brushes.Blue))
      {
          //
      }
     ④针对初始化对个不同类型的变量时,可以都声明为IDisposable类型,例如:
        using (IDisposable font = new Font("Verdana", 12), pen = new Pen(Brushes.Black))
      {
          float size = (font as Font).Size;
          Brush brush = (pen as Pen).Brush;
      }
 
using实质 try-finally Dispose
    在程序编译阶段,编译器会自动将using语句生成为try-finally语句,并在finally块中调用对象的Dispose方法,来清理资源。所以,using语句等效于try-finally语句,例如:
    Font f2 = new Font("Arial", 10, FontStyle.Bold);
  try
  {
      //执行文本绘制操作
   }
  finally
  {
      if (f2 != null) ((IDisposable)f2).Dispose();
  } 

 

1.4 值类型和引用类型

值类型派生自ValueType,分配在栈上,生命周期由作用域决定。引用类型分配在托管堆上。引用类型指向被分配的实例所占的内存。

常见引用类型有:数组、字符串、接口、委托、object等。

参数的默认传递类型:按值传递。值类型传递值,引用类型,传递的存放地址。按值类型传递引用,可改变对象的状态和数据,不能改变引用的地址。按引用传递引用,可改变对象的状态和 引用的对象,即引用的地址。

 

拆箱和装箱:引用类型和值类型的相互转换叫做拆装箱操作。

拆箱:拆箱就是将一个引用型对象转换成任意值型!比如:

int i=0;
System.Object obj=i;
int j=(int)obj;

 装箱:装箱就是隐式的将一个值型转换为引用型对象。比如:

int i=0;
Syste.Object obj=i;

 

1.5 常量

Const——定义时初始化。

Readonly——可以在构造函数中初始化。

Static readonly——可以初始化,也可以在静态构造函数中初始化。

1.6 集合

非泛型集合的性能和类型安全问题。

性能——有些集合类操作元素时,要求元素必须是引用类型,所以对值类型会进行装箱操作。这种堆栈内存转移会导致性能问题。

类型安全——拆箱时,必须将数据拆箱为装箱之前声明的类型。System.Collections中的大多数类操作的都是object。

 

使用泛型可以解决上面的问题。

1.7 委托和事件

委托——类型安全的C函数指针,派生自MulticaseDelegate.

 

1.8 溢出检查checked

checked((byte)Add(b1,b2))  

 

try

checked{

   sum=(byte)Add(b1,b2);

}

Catch(OverflowException e)

 

2   高级特性

 

2.1 程序集

CIL + 元数据。

类型元数据:描述类(型)和类(型)的成员。

程序集元数据:清单。本程序集信息和需要的外部程序集。

 

2.2 应用程序域

 

 
   

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

进程(Process)是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。进程之间是相对独立的,一个进程无法直接访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。

 

使用.NET建立的可执行程序 *.exe,并没有直接承载到进程当中,而是承载到应用程序域(AppDomain)当中.应用程序域是.NET引入的一个新概念,它比进程所占用的资源要少,可以被看作是一个轻量级的进程。

 

    在一个进程中可以包含多个应用程序域,一个应用程序域可以装载一个可执行程序(*.exe)或者多个程序集(*.dll),这样可以使应用程序域之间实现深度隔离,即使进程中的某个应用程序域出现错误,也不会影响其他应用程序域的正常运作。

 

    当一个程序集同时被多个应用程序域调用时,会出现两种情况:

    第一种情况:CLR分别为不同的应用程序域加载此程序集。

    第二种情况:CLR把此程序集加载到所有的应用程序域之外,并实现程序集共享,此情况比较特殊,被称作为Domain Neutral.

————————————————

版权声明:本文为CSDN博主「zhulongxi」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/zhulongxi/article/details/51612233

 

2.3 对象上下文

https://blog.csdn.net/zhulongxi/article/details/51612233

应用程序域是进程中承载程序集的逻辑分区,在应用程序域中存在更细粒度的用于承载.NET对象的实体,即.NET上下文Context.所有的.NET对象都存在于.NET上下文当中,每个AppDomain当中至少存在一个默认上下文(context0),:一般类所建立的对象为上下文灵活对象(context-agile),它们都由CLR自动管理,可存在于任意的上下文当中。

 

                    

 

2.透明代理:

 

    在上下文的接口当中存在着一个消息接收器负责检测拦截和处理信息,当对象是MarshalByRefObject的子类的时候,CLR将会建立透明代理,实现对象与消息之间的转换。

    应用程序域是CLR中资源的边界,一般情况下,应用程序域中的对象不能被外界的对象所访问。而MarshalByRefObject 的功能就是允许在支持远程处理的应用程序中跨应用程序域边界访问对象,在使用.NET Remoting远程对象开发时经常使用到的一个父类。

 

3.上下文绑定:

 

    当系统需要对象使用消息接收器机制的时候,即可使用ContextBoundObject类。ContextBoundObject继承了MarshalByRefObject类,保证了它的子类都会通过透明代理被访问。

 

    一般类所建立的对象为上下文灵活对象(context-agile),它们都由CLR自动管理,可存在于任意的上下文当中。而 ContextBoundObject 的子类所建立的对象(称作上下文绑定对象)只能在建立它的对应上下文中正常运行,此状态被称为上下文绑定。其他对象想要访问ContextBoundObject 的子类对象时,都只能通过代透明理来操作。ContextBound还有一个Synchronization特性,此特性会保证ContextBound对象被加载到一个线程安全的上下文当中运行

 

 class Program

    {

        static void Main(string[] args)

        {

            Example example = new Example();

            example.Test();

            MycontextBound bouind = new MycontextBound();

            bouind.Test("ContextBound Test");

            example.Sync(bouind);

            Console.ReadKey();

        }

 

        public class Example

        {

            public void Test()

            {

                ContextMessage("Example test\n");

            }

            public void Sync(<strong>MycontextBound contextBound</strong>)//传入MyContextBound的代理

            {

                contextBound.Test("Example call on ContextBound\n");

            }

        }

 

        [Synchronization]

        public class MycontextBound : ContextBoundObject

        {

            public void Test(string message)

            {

                ContextMessage(message);

            }

        }

 

        //显示上下文信息

        public static void ContextMessage(string data)

        {

            Context context = Thread.CurrentContext;

            Console.WriteLine(string.Format("{0}ContextId is {1}", data, context.ContextID));

            foreach (var prop in context.ContextProperties)

                Console.WriteLine(prop.Name);

            Console.WriteLine();

        }

 

    }

如果在Example类中直接用MycontextBound的示例调用其test方法, 则无法调用,只能通过代理来访问MycontextBound对象。

 

 

2.4 操作符重载

 

2.5 扩展方法

为类型新增方法。比如,类库中某个类,需要增加一个方法方便使用。

public static 返回类型 函数名(this 类型  入参名,入参1类型  入参1值)

2.6 对象生命周期

 

 

2.7 反射

 

https://www.cnblogs.com/vaevvaev/p/6995639.html

2.8 特性

 

 

 

 

2.9 动态类型

posted on 2019-12-15 15:00  Zachagy  阅读(59)  评论(0)    收藏  举报