.NET--第5部分ppt课件_第1页
.NET--第5部分ppt课件_第2页
.NET--第5部分ppt课件_第3页
.NET--第5部分ppt课件_第4页
.NET--第5部分ppt课件_第5页
已阅读5页,还剩59页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、第一部分 Microsoft.NET 框架基本原理 第二部分 类型与通用语言运行时 第三部分 类型设计 第四部分 基本类型 第五部分 类型管理,第五部分 类型管理 异常 自动内存管理,第18章 异常,18.1 异常处理的概念,看以下代码: class Program static void Main(string args) int values=new int 10 ; for( int i=1; i=10; i+ ) Console.WriteLine( valuesi ) ; Console.ReadKey(); ,18.1 异常处理的概念,看以下代码: class Program st

2、atic void Main(string args) int values=new int 10 ; for( int i=1; i=10; i+ ) Console.WriteLine( valuesi ) ; Console.ReadKey(); ,C#中数组下标从0开始,因而此代码在运行时将会发生一个“数组访问越界”错误。,类似于这种在程序中隐藏的错误,被称为“异常(Exception)”,它表明程序执行期间出现了一个非法的运行状况,期望程序进行的某项操作没能完成。 注意,异常是在程序运行时间出现的,不是在编译期间出现的,编译器在编译时发现的错误是语法错误,不能称之为异常。 .NET

3、Framework 提供了多个异常类,在编程中常用的有以下几个:,异常处理的目的是根据不同的异常情况提供不同的处置方法,使程序更稳定、更安全。 异常处理的主要用途是提供准确的错误消息,解释失败的原因、位置和错误类型等,同时提供一定的恢复能力,尽可能地降低出错的机率。,使用try、catch 和 finally 处理异常的结构如下: try / / 可能引发异常的语句-try语句块(1) catch(Exception e) / / 对异常进行处理的语句-catch语句块(2) finally / / “打扫战场”的语句-finally语句块(3) ,18.2 编程实现异常处理,18.2.1 t

4、ry、catch 和 finally,异常处理机制将代码分成3大块。第(1)块是正常执行程序功能的语句块,其中有可能在运行时引发错误。第(2)块在程序正常运行时不会执行,仅当有异常出现时转到此块执行。第(3)块不管程序执行时有无异常出现都会执行。 当程序正常运行时,程序的执行流程为: 第(1)块 第(3)块 当第(1)块中有代码引发了一个异常时,程序的执行流程为: 第(1)块 第(2)块 第(3)块 可以有多个catch语句块,每个代码块捕获一种异常(由catch后的参数类型决定,称为“异常筛选器(Exception Filter)”)。.NET Framework中使用catch语句只能捕获

5、Exception类及其子类的对象。 当一个异常被引发后,如果应用程序没有提供合适的异常处理代码,应用程序进程将会被CLR强制中止。,异常处理机制将代码分成3大块。第(1)块是正常执行程序功能的语句块,其中有可能在运行时引发错误。第(2)块在程序正常运行时不会执行,仅当有异常出现时转到此块执行。第(3)块不管程序执行时有无异常出现都会执行。 当程序正常运行时,程序的执行流程为: 第(1)块 第(3)块 当第(1)块中有代码引发了一个异常时,程序的执行流程为: 第(1)块 第(2)块 第(3)块 可以有多个catch语句块,每个代码块捕获一种异常(由catch后的参数类型决定,称为“异常筛选器(

6、Exception Filter)”)。.NET Framework中使用catch语句只能捕获Exception类及其子类的对象。 当一个异常被引发后,如果应用程序没有提供合适的异常处理代码,应用程序进程将会被CLR强制中止。,异常处理机制将代码分成3大块。第(1)块是正常执行程序功能的语句块,其中有可能在运行时引发错误。第(2)块在程序正常运行时不会执行,仅当有异常出现时转到此块执行。第(3)块不管程序执行时有无异常出现都会执行。 当程序正常运行时,程序的执行流程为: 第(1)块 第(3)块 当第(1)块中有代码引发了一个异常时,程序的执行流程为: 第(1)块 第(2)块 第(3)块 可以

7、有多个catch语句块,每个代码块捕获一种异常(由catch后的参数类型决定,称为“异常筛选器(Exception Filter)”)。.NET Framework中使用catch语句只能捕获Exception类及其子类的对象。 当一个异常被引发后,如果应用程序没有提供合适的异常处理代码,应用程序进程将会被CLR强制中止。,注意:指定由逗号分开的catch参数表是语法错误,catch只能有一个参数,即一条catch语句只能捕获此参数限定的那种类型的异常。另外,在某个try块后有两个不同的catch块捕获两个相同类型的异常也是语法错误。 finally语句块是可选的,主要用于解决资源泄露问题,它

8、位于catch语句块之后,CLR保证它们一定执行。 注意:finally语句块中也可能发生异常,如果这种情况发生,先前的异常被放弃。 程序中也可使用 throw 关键字主动地抛出一个异常: throw new Exception(“我的新异常对象”);,上节中引发“数组访问越界”错误的代码可以用异常处理机制重写如下: class Program static void Main(string args) int values=new int 10 ; try for( int i=1; i=10; i+ ) Console.WriteLine( valuesi ) ; ,catch (Inde

9、xOutOfRangeException e) Console.WriteLine(“在输出values数组值时发生数组越界错误”); Console.WriteLine(“异常种类: ” + e.GetType().Name ); Console.WriteLine(“系统给出的出错信息: ” + e.Message ); Console.WriteLine(“系统调用堆栈信息: ” + e.StackTrace ); Console.WriteLine(“引发此错误的方法: ” + e.TargetSite ); Console.ReadKey( ); ,当程序运行时,异常提示信息如下:

10、在输出values数组值时发生数组越界错误 异常种类:IndexOutOfRangeException 系统给出的出错信息:索引超出了数组界限 系统调用堆栈信息:在 OnlyTest.Program.Main(String args) 位置 C:OnlyTestProgram.cs: 行号 11 引发此错误的方法:Void Main(System.String ),.NET Framework异常处理的核心是Exception类,它是所有可捕获异常类的基类,程序发生异常时,CLR 会创建一个相应种类的异常对象来表示该异常。 Exception 对象 e 的3个重要属性。,.NET Framew

11、ork 实现的异常处理具有以下特点: 处理异常时不用考虑生成异常的语言或处理异常的语言。 异常处,理时不要求任何特定的语言语法,而是允许每种语言定义自己的语法。 允许跨进程甚至跨计算机边界引发异常。 为达以上目的,CLR为每个正在运行的程序创建了一个异常信息表。在异常信息表中,程序中每个方法都有一个关联的异常处理信息数组。如果方法中有受到保护的语句块,则此方法相关联的异常处理信息数组中就记录了当异常发生时,CLR自动调用异常处理代码所需的相关信息。 如果某方法中没有受保护块,则其对应的异常处理信息数组为空。 提示:被 trycatch 包围的语句块称为受保护块。,18.2.2 CLR 结构化异

12、常处理原理,当某一方法中发生异常时,CLR 在此方法对应的异常处理信息数组中搜索,以确定是哪一个受保护块引发的异常,以及应该由哪个catch块处理。 (1)如果找到以上信息,CLR创建一个Exception对象(或其子类对象)来描述该异常。然后,CLR执行处理该异常的catch语句块,如果有finally语句块,接着执行finally语句块。 (2)如果在当前方法中没有找到相关信息,则CLR搜索当前方法的每一个调用方,在调用者的异常处理信息数组中搜索,直到最顶层的调用者。这个由底向上的搜索过程,其信息被记录在一个堆栈中,称为“异常堆栈”。 (3)如果任何调用者都没有处理这种异常的代码,则CLR

13、允许使用一个调试器来处理该异常。如果用户放弃调试,则CLR引发一个UnhandledException事件,而这时如果应用程序也没有编写响应UnhandledException事件的代码,则CLR会结束此进程。,public void SomeMethod() try / / 这里执行一些操作 catch (NullReferenceException e) / / 处理一个空引用异常 catch (InvalidCastException e) / / 处理一个无效转型异常 catch / / 在C#中,该筛选器会捕获任何异常 / / 处理所有异常 ,首先创建一个自定义的异常类MyExcep

14、tion。 class MyException : Exception public MyException(String info) : base(info) 接着,编写代码实现以下的方法调用链,在SomeFunc()方法中引发一个Exception异常。 Main( )FuncInvoker( )SomeFunc( ) 具体代码如下:,异常的传播过程,static void Main(string args) FuncInvoker( ); static void FuncInvoker( ) SomeFunc( ); static void SomeFunc( ) throw new M

15、yException(“主动引发的异常”); ,异常的传播过程,可以在整个异常“传输链”中的任何一环“打断”整个异常传输,以避免进入调试阶段。 修改 FuncInvoker 函数: static void FuncInvoker( ) try SomeFunc(); catch (MyException e) Console.WriteLine(程序中出现了异常); Console.WriteLine(其信息为:+e.Message ); ,如果最底层的SomeFunc()函数引发的不是MyException异常,而是其他类型的异常,则FuncInvoker方法中的trycatch块又不管用了

16、,还是会引发CLR报告错误。 解决方法:可以在FuncInvoker方法中再增加一个catch语句块,专门处理此种类型的异常。,由于程序中可以引发的异常种类很多,很难一一写代码处理,最保险的方法是在最顶层方法中捕获Exception异常。 static void Main(string args) try FuncInvoker(); catch (Exception e) Console.WriteLine(e.Message); ,第19章 自动内存管理 (垃圾收集),19.1 垃圾收集平台基本原理解析,访问一个资源所需要的几个步骤: 1. 调用中间语言(IL)中的 newobj 指令,为

17、表示某个特定资源的类型实例分配一定的内存空间。 2. 初始化上一步所得的内存,设置资源的初始状态,从而使其可以为程序所用。一个类型的实例构造器负责做这样的初始化工作。 3. 通过访问类型成员来使用资源,这根据需要会有一些反复。 4. 销毁资源状态,执行清理工作。 5. 释放内存。这一步由垃圾收集器全权负责。,以上模式却是导致许多编程错误的主要原因之一。 释放无用的内存、试图访问已经被释放的内存,这两类bug发生的时间 和次序都难以预料;这两类bug的直接后果是资源泄露(内存消耗)和对象损 毁(状态不稳定)。 正确无误的资源管理通常是一件比较困难和单调的工作,它们极大地分 散开发人员解决实际问题

18、的注意力。垃圾收集(garbage collection) 机 制能够简化这种容易遗漏的内存管理任务。 大多数类型表示的资源并不需要任何特殊的清理操作。 对于一个表示(或者说封装)着非托管(操作系统)资源的类型,在其对象被 销毁时,就必须执行一些清理代码。,CLR 要求所有的内存资源都从托管堆(managed heap) 分配而得。 当应用程序进程完成初始化后,CLR 将保留(reserve)一块连续的地址空间,这段空间最初并不对应任何的物理内存,该地址空间即为托管堆。 托管堆上维护着一个指针,称为 NextObjPtr 。该指针标识着下一个新建对象分配时在托管堆中所处的位置。刚开始时,Nex

19、tObjPtr 被设为CLR 保留地址空间的基地址。,内存分配和资源初始化问题,中间语言(IL)指令 newobj 负责创建新的对象。在代码运行时,newobj指令将导致CLR执行以下几步操作: 1. 计算类型所有字段(以及其基类所有的字段)所需要的字节总数。 2. 在前面所得字节总数的基础上再加上对象额外的附加成员所需的字节数。每个对象包括两个附加字段:一个方法表指针和一个SyncBlockIndex。 3. CLR检查保留区域中的空间是否满足分配新对象所需的字节数如需要则提交物理内存。如果满足,对象将被分配在NextObjPtr指针所指示的地方。接着,类型的实例构造器被调用,IL指令new

20、obj返回为其分配的内存地址。就在newobj指令返回新对象的地址之前,NextObjPtr指针会越过新对象所处的内存区域,并指示出下一个新建对象在托管堆中的地址。,对比C语言运行时中的堆分配内存时的情况。 普通堆中,如果连续地创建几个对象,很可能被分散在地址空间的各个角落。但在托管堆中,连续分配的对象可以保证它们在内存中也是连续的。,包含3个对象的托管堆,托管堆在实现的简单性和速度方面要优于C语言运行时中的堆。假设于应用程序的地址空间和存储空间是无限的。 托管堆必须应用某种机制来允许做这样的假设。这种机制就是垃圾收集器。 垃圾收集器工作原理: 当应用程序调用 new 操作符创建对象时,托管堆

21、中可能没有足够的地址空间来分配该对象。托管堆通过将对象所需要的字节总数添加到NextObjPtr 指针表示的地址上来检测这种情况。如果得到的结果超出了托管堆的地址空间范围,那么托管堆将被认为已经充满,这时就需要执行垃圾收集。,每个应用程序都有一组根(root)。一个根是一个存储位置,其中包含着一个指向引用类型的内存指针。该指针或者指向一个托管堆中的对象,或者被设为null。 所有全局引用类型变量或静态引用类型变量都被认为是根。一个线程堆栈上所有引用类型的本地变量或者参数变量也被认为是一个根。在一个方法内,指向引用类型对象的CPU寄存器也被认为是一个根。 当JIT编译器编译一个方法的IL代码时,

22、除了产生本地CPU代码外,JIT编译器还会创建一个内部的表。从逻辑上讲,该表中的每一个条目都标识着一个方法的本地CPU指令的字节偏移范围,以及该范围中一组包含根的内存地址。内部表结构如图:,19.2 垃圾收集算法,0 x00000000 0 x00000020 this,arg1,arg2,ECX,EDX 0 x00000021 0 x00000122 this,arg2,fs,EBX 0 x00000123 0 x00000145 fs,起始字节偏移 结尾字节偏移 根,如果在0 x00000021和0 x00000122 之间的代码执行时开始了垃圾收集,那么垃圾收集器将知道参数this、参数

23、arg2、本地变量fs以及寄存器EBX都是根,它们引用的托管堆中的对象将不会被认为是可收集的垃圾对象。除此之外,垃圾收集器还可以遍历线程的调用堆栈,通过检测其中每一个方法的内部表来确定所有调用方法中的根。最后,垃圾收集器使用其他一些手段来获得存储在全局引用类型变量和静态引用类型变量中保存的根。,JIT编译器生成的表,展示了本地代码偏移和方法中根的映射关系,当垃圾收集器开始执行时,它首先假设托管堆中所有的对象都是可收集的垃圾。然后,垃圾收集器遍历所有的根,构造出一个包含所有可达对象的图。 例:,上图展示了一个分配有几个对象的托管堆,其中对象A、C、D和F为应用程序的根所直接引用。所有这些对象都是

24、可达对象图的一部分。当对象D被添加到该图中时,垃圾收集器注意到它还引用着对象H,于是对象H也被添加到该图中。垃圾收集器就这样以递归的方式来遍历应用程序中所有的可达对象。 例:,垃圾收集器接着线性地遍历托管堆以寻找包含可收集垃圾对象的连续区块。如果找到了较大的连续区块,垃圾收集器将会把内存中的一些非垃圾对象移到这些连续区块中以压缩托管堆。 搬移内存中的对象将使所有指向这些对象的指针变得无效,所以垃圾收集器必须修改应用程序的根以使它们指向这些对象更新后的位置。 在托管堆中的内存被压缩之后,托管堆上的NextObjPtr 指针将被设为指向最后一个非垃圾对象之后。 垃圾收集执行后的托管堆如下图:,A

25、C D F H,NextObjPtr,根 全局变量 静态变量 本地变量 CPU寄存器,垃圾收集执行后的托管堆,两点重要认识: 首先,不必再自己实现代码来管理应用程序中对象的生存期。 其次,前面描述的bug将不复存在。 因为任何不可从应用程序的根中访问的对象都会在某个时刻被收集,所以应用程序不可能再发生内存泄漏的情况。另外,应用程序也不能再访问已经被释放的对象。因为如果对象可达,它将不可能被释放;而如果对象不可达,应用程序必将无法访问到它。,下面代码演示了垃圾收集器是怎样分配和管理对象的。,class App static void Main( ) ArrayList a = new Array

26、List( ) ; for (Int32 x = 0; x 10000; x+) a.Add(new Object( ) ; Console.WriteLine(a.count) ; Console.WriteLine(“End of method”) ; ,任何封装了非托管资源的类型,例如:文件、网络链接、套接字、互斥体等,都必须支持一种称作终止化(finalization)的操作。 终止化操作允许一种资源在它所占用的内存被回收之前首先执行一些清理工作。 要提供终止化操作,必须为类型实现一个名为 Finalize 的方法。 当垃圾收集器判定一个对象为可收集的垃圾时,它便会调用该对象的Fina

27、lize方法(如果存在的话)。 如果一个封装了非托管资源的类型没有定义Finalize方法,那么这些非托管资源将得不到关闭,从而会导致某种程度的资源泄漏(前提是没有显式关闭对象所封装的非托管资源)。直到进程结束,这些托管资源才会被操作系统回收。,19.3 终止化操作,public sealed class OSHandle private IntPtr handle; public OSHandle(IntPtr handle) this.handle = handle; protected override void Finalize() try CloseHandle(handle) ;

28、finally base.Finalize() ; public IntPtr ToHandle() return handle; public static implicit operator IntPtr(OSHandle osHandle) return osHandle.ToHandle(); private extern static Boolean CloseHandle(IntPtr handle); ,定义一个封装着非托管资源的类型,public sealed class OSHandle private IntPtr handle; public OSHandle(IntPt

29、r handle) this.handle = handle; / / 当垃圾收集执行时,下面的析构器(Finalize)方法将被 / / 调用,它将关闭非托管资源句柄 OSHandle() ColseHandle(handle) ; public IntPtr ToHandle() return handle; public static implicit operator IntPtr(OSHandle osHandle) return osHandle.ToHandle(); private extern static Boolean CloseHandle(IntPtr handle)

30、; ,C#为定义Finalize方法提供了特殊的语法:,终止化操作的内部机理: 当应用程序创建一个新对象时,new 操作符会为对象从托管堆上分配内存。如果该对象的类型定义了Finalize方法,那么在该类型的实例构造器运行之前,指向该对象的一个指针将被放到一个称作终止化链表的数据结构里面。终止化链表是一个由垃圾收集器控制的内部数据结构。链表上的每一个条目都引用着一个对象,这实际是在告诉垃圾收集器在回收这些对象的内存之前先要调用它们的Finalize方法。,一个包含几个对象的托管堆。有些是从应用程序的根可达的对象,有些不是。当对象C、E、F、I和J被创建时,系统会检测到这些对象的类型定义了Fin

31、alize方法,于是将指向这些对象的指针添加到终止化链表中。,A B C D E F G H I J,根 全局变量 静态变量 本地变量 CPU寄存器,终止化可达队列,当垃圾收集开始时,对象B、E、G、H、I和J为垃圾对象。垃圾收集器然后扫描终止化链表以查找其中是否有指向这些对象的指针。当找到这样的指针时,它们会被从终止化链表中移除,并添加到终止化可达队列。终止化可达队列中出现的对象表示该对象的Finalize方法即将被调用。,A C D E F I J,根 全局变量 静态变量 本地变量 CPU寄存器,终止化链表,C F,E I J,终止化可达队列,Finalize方法是.NET内部的一个释放内

32、存资源的方法。这个方法不对外公开,由垃圾收集器自己调用。 Finalize方法可以确保托管对象在释放内存的同时不会泄漏非托管资源。 问题: 不能确定该方法会在何时被调用,而且由于它并不是一个公有方法,所以也不能显式地调用它。,要提供显式释放或者关闭对象的能力,一个类型通常要实现一种被称为Dispose的模式。 Dispose 模式定义了开发人员在实现类型的显式资源清理功能时所要遵循的一些约定。如果一个类型实现了Dispose 模式,使用该类型的开发人员将能够知道当对象不再被使用时如何显式地释放掉它所占用的资源。 Dispose 调用方法:要释放的资源对象.Dispose 调用Dispose方法

33、释放对象所封装的非托管资源。但并不会释放对象在托管堆中占用的内存资源,释放对象内存的工作仍由垃圾收集器负责,而且释放的时间仍不确定。,19.4 Dispose模式:强制对象清理资源,Close 方法 和 Dispose 一样,只不过有的对象没有提供Dispose方法,只提供了Close方法,而Close其实在对象的类中,依然是调用了一个私有的Dispose方法。 总结: .NET 中提供了三种模式来回收内存资源:Dispose模式,Finalize方法、Close方法。,垃圾收集会给应用程序带来不小的性能损伤,CLR 的垃圾收集器提供了一些特殊的优化设计来大幅度提高垃圾收集的性能。 代龄是旨在

34、提高垃圾收集器性能的一种机制。 一个基于代龄的垃圾收集器有以下几点假设: (1)对象越新,其生存期越短。 (2)对象越老,其生存期越长。 (3)对托管堆的一部分执行垃圾收集要比对整个托管堆执行垃圾收集速度更快。,19.5 对象的代龄,在托管堆初始化时,其中不包括任何对象。这时添加到托管堆的对象被称为第 0 代对象。简单的说,第 0 代对象就是那些新构造的对象,垃圾收集器还没有对它们执行过任何检查。 新启动的应用程序中托管堆情况:分配有5个对象,经一段时间后,对象C和E将变为不可达对象。 当CLR初始化时,它会为第 0 代对象选择一个阙值容量,假定为256K。当分配新对象导致第 0 代对象超过了为其设定的阙值容量时,垃圾收集器就必须启动了。,代龄的工作机制,假设从对象A到E总共占用了256KB,那么当对象F被分配时,垃圾收集器就会启动。垃圾收集器判定对象C和E为垃圾对象,会压缩对象D使其邻接于对象B。此次垃圾收集中存活下来的对象被认为是第 1 代对象。 经过一轮垃圾收

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论