NET课件第5部分.ppt
- 文档编号:18716875
- 上传时间:2023-10-17
- 格式:PPT
- 页数:64
- 大小:253.50KB
NET课件第5部分.ppt
《NET课件第5部分.ppt》由会员分享,可在线阅读,更多相关《NET课件第5部分.ppt(64页珍藏版)》请在冰点文库上搜索。
第一部分Microsoft.NET框架基本原理第二部分类型与通用语言运行时第三部分类型设计第四部分基本类型第五部分类型管理,第五部分类型管理异常自动内存管理,第18章异常,18.1异常处理的概念,看以下代码:
classProgramstaticvoidMain(stringargs)intvalues=newint10;for(inti=1;i=10;i+)Console.WriteLine(valuesi);Console.ReadKey();,18.1异常处理的概念,看以下代码:
classProgramstaticvoidMain(stringargs)intvalues=newint10;for(inti=1;i=10;i+)Console.WriteLine(valuesi);Console.ReadKey();,C#中数组下标从0开始,因而此代码在运行时将会发生一个“数组访问越界”错误。
类似于这种在程序中隐藏的错误,被称为“异常(Exception)”,它表明程序执行期间出现了一个非法的运行状况,期望程序进行的某项操作没能完成。
注意,异常是在程序运行时间出现的,不是在编译期间出现的,编译器在编译时发现的错误是语法错误,不能称之为异常。
.NETFramework提供了多个异常类,在编程中常用的有以下几个:
异常处理的目的是根据不同的异常情况提供不同的处置方法,使程序更稳定、更安全。
异常处理的主要用途是提供准确的错误消息,解释失败的原因、位置和错误类型等,同时提供一定的恢复能力,尽可能地降低出错的机率。
使用try、catch和finally处理异常的结构如下:
try/可能引发异常的语句-try语句块
(1)catch(Exceptione)/对异常进行处理的语句-catch语句块
(2)finally/“打扫战场”的语句-finally语句块(3),18.2编程实现异常处理,18.2.1try、catch和finally,异常处理机制将代码分成3大块。
第
(1)块是正常执行程序功能的语句块,其中有可能在运行时引发错误。
第
(2)块在程序正常运行时不会执行,仅当有异常出现时转到此块执行。
第(3)块不管程序执行时有无异常出现都会执行。
当程序正常运行时,程序的执行流程为:
第
(1)块第(3)块当第
(1)块中有代码引发了一个异常时,程序的执行流程为:
第
(1)块第
(2)块第(3)块可以有多个catch语句块,每个代码块捕获一种异常(由catch后的参数类型决定,称为“异常筛选器(ExceptionFilter)”)。
.NETFramework中使用catch语句只能捕获Exception类及其子类的对象。
当一个异常被引发后,如果应用程序没有提供合适的异常处理代码,应用程序进程将会被CLR强制中止。
异常处理机制将代码分成3大块。
第
(1)块是正常执行程序功能的语句块,其中有可能在运行时引发错误。
第
(2)块在程序正常运行时不会执行,仅当有异常出现时转到此块执行。
第(3)块不管程序执行时有无异常出现都会执行。
当程序正常运行时,程序的执行流程为:
第
(1)块第(3)块当第
(1)块中有代码引发了一个异常时,程序的执行流程为:
第
(1)块第
(2)块第(3)块可以有多个catch语句块,每个代码块捕获一种异常(由catch后的参数类型决定,称为“异常筛选器(ExceptionFilter)”)。
.NETFramework中使用catch语句只能捕获Exception类及其子类的对象。
当一个异常被引发后,如果应用程序没有提供合适的异常处理代码,应用程序进程将会被CLR强制中止。
异常处理机制将代码分成3大块。
第
(1)块是正常执行程序功能的语句块,其中有可能在运行时引发错误。
第
(2)块在程序正常运行时不会执行,仅当有异常出现时转到此块执行。
第(3)块不管程序执行时有无异常出现都会执行。
当程序正常运行时,程序的执行流程为:
第
(1)块第(3)块当第
(1)块中有代码引发了一个异常时,程序的执行流程为:
第
(1)块第
(2)块第(3)块可以有多个catch语句块,每个代码块捕获一种异常(由catch后的参数类型决定,称为“异常筛选器(ExceptionFilter)”)。
.NETFramework中使用catch语句只能捕获Exception类及其子类的对象。
当一个异常被引发后,如果应用程序没有提供合适的异常处理代码,应用程序进程将会被CLR强制中止。
注意:
指定由逗号分开的catch参数表是语法错误,catch只能有一个参数,即一条catch语句只能捕获此参数限定的那种类型的异常。
另外,在某个try块后有两个不同的catch块捕获两个相同类型的异常也是语法错误。
finally语句块是可选的,主要用于解决资源泄露问题,它位于catch语句块之后,CLR保证它们一定执行。
注意:
finally语句块中也可能发生异常,如果这种情况发生,先前的异常被放弃。
程序中也可使用throw关键字主动地抛出一个异常:
thrownewException(“我的新异常对象”);,上节中引发“数组访问越界”错误的代码可以用异常处理机制重写如下:
classProgramstaticvoidMain(stringargs)intvalues=newint10;tryfor(inti=1;i=10;i+)Console.WriteLine(valuesi);,catch(IndexOutOfRangeExceptione)Console.WriteLine(“在输出values数组值时发生数组越界错误”);Console.WriteLine(“异常种类:
”+e.GetType().Name);Console.WriteLine(“系统给出的出错信息:
”+e.Message);Console.WriteLine(“系统调用堆栈信息:
”+e.StackTrace);Console.WriteLine(“引发此错误的方法:
”+e.TargetSite);Console.ReadKey();,当程序运行时,异常提示信息如下:
在输出values数组值时发生数组越界错误异常种类:
IndexOutOfRangeException系统给出的出错信息:
索引超出了数组界限系统调用堆栈信息:
在OnlyTest.Program.Main(Stringargs)位置C:
OnlyTestProgram.cs:
行号11引发此错误的方法:
VoidMain(System.String),.NETFramework异常处理的核心是Exception类,它是所有可捕获异常类的基类,程序发生异常时,CLR会创建一个相应种类的异常对象来表示该异常。
Exception对象e的3个重要属性。
.NETFramework实现的异常处理具有以下特点:
处理异常时不用考虑生成异常的语言或处理异常的语言。
异常处,理时不要求任何特定的语言语法,而是允许每种语言定义自己的语法。
允许跨进程甚至跨计算机边界引发异常。
为达以上目的,CLR为每个正在运行的程序创建了一个异常信息表。
在异常信息表中,程序中每个方法都有一个关联的异常处理信息数组。
如果方法中有受到保护的语句块,则此方法相关联的异常处理信息数组中就记录了当异常发生时,CLR自动调用异常处理代码所需的相关信息。
如果某方法中没有受保护块,则其对应的异常处理信息数组为空。
提示:
被trycatch包围的语句块称为受保护块。
18.2.2CLR结构化异常处理原理,当某一方法中发生异常时,CLR在此方法对应的异常处理信息数组中搜索,以确定是哪一个受保护块引发的异常,以及应该由哪个catch块处理。
(1)如果找到以上信息,CLR创建一个Exception对象(或其子类对象)来描述该异常。
然后,CLR执行处理该异常的catch语句块,如果有finally语句块,接着执行finally语句块。
(2)如果在当前方法中没有找到相关信息,则CLR搜索当前方法的每一个调用方,在调用者的异常处理信息数组中搜索,直到最顶层的调用者。
这个由底向上的搜索过程,其信息被记录在一个堆栈中,称为“异常堆栈”。
(3)如果任何调用者都没有处理这种异常的代码,则CLR允许使用一个调试器来处理该异常。
如果用户放弃调试,则CLR引发一个UnhandledException事件,而这时如果应用程序也没有编写响应UnhandledException事件的代码,则CLR会结束此进程。
publicvoidSomeMethod()try/这里执行一些操作catch(NullReferenceExceptione)/处理一个空引用异常catch(InvalidCastExceptione)/处理一个无效转型异常catch/在C#中,该筛选器会捕获任何异常/处理所有异常,首先创建一个自定义的异常类MyException。
classMyException:
ExceptionpublicMyException(Stringinfo):
base(info)接着,编写代码实现以下的方法调用链,在SomeFunc()方法中引发一个Exception异常。
Main()FuncInvoker()SomeFunc()具体代码如下:
异常的传播过程,staticvoidMain(stringargs)FuncInvoker();staticvoidFuncInvoker()SomeFunc();staticvoidSomeFunc()thrownewMyException(“主动引发的异常”);,异常的传播过程,可以在整个异常“传输链”中的任何一环“打断”整个异常传输,以避免进入调试阶段。
修改FuncInvoker函数:
staticvoidFuncInvoker()trySomeFunc();catch(MyExceptione)Console.WriteLine(程序中出现了异常);Console.WriteLine(其信息为:
+e.Message);,如果最底层的SomeFunc()函数引发的不是MyException异常,而是其他类型的异常,则FuncInvoker方法中的trycatch块又不管用了,还是会引发CLR报告错误。
解决方法:
可以在FuncInvoker方法中再增加一个catch语句块,专门处理此种类型的异常。
由于程序中可以引发的异常种类很多,很难一一写代码处理,最保险的方法是在最顶层方法中捕获Exception异常。
staticvoidMain(stringargs)tryFuncInvoker();catch(Exceptione)Console.WriteLine(e.Message);,第19章自动内存管理(垃圾收集),19.1垃圾收集平台基本原理解析,访问一个资源所需要的几个步骤:
1.调用中间语言(IL)中的newobj指令,为表示某个特定资源的类型实例分配一定的内存空间。
2.初始化上一步所得的内存,设置资源的初始状态,从而使其可以为程序所用。
一个类型的实例构造器负责做这样的初始化工作。
3.通过访问类型成员来使用资源,这根据需要会有一些反复。
4.销毁资源状态,执行清理工作。
5.释放内存。
这一步由垃圾收集器全权负责。
以上模式却是导致许多编程错误的主要原因之一。
释放无用的内存、试图访问已经被释放的内存,这两类bug发生的时间和次序都难以预料;这两类bug的直接后果是资源泄露(内存消耗)和对象损毁(状态不稳定)。
正确无误的资源管理通常是一件比较困难和单调的工作,它们极大地分散开发人员解决实际问题的注意力。
垃圾收集(garbagecollection)机制能够简化这种容易遗漏的内存管理任务。
大多数类型表示的资源并不需要任何特殊的清理操作。
对于一个表示(或者说封装)着非托管(操作系统)资源的类型,在其对象被销毁时,就必须执行一些清理代码。
CLR要求所有的内存资源都从托管堆(managedheap)分配而得。
当应用程序进程完成初始化后,CLR将保留(reserve)一块连续的地址空间,这段空间最初并不对应任何的物理内存,该地址空间即为托管堆。
托管堆上维护着一个指针,称为NextObjPtr。
该指针标识着下一个新建对象分配时在托管堆中所处的位置。
刚开始时,NextObjPtr被设为CLR保留地址空间的基地址。
内存分配和资源初始化问题,中间语言(IL)指令newobj负责创建新的对象。
在代码运行时,newobj指令将导致CLR执行以下几步操作:
1.计算类型所有字段(以及其基类所有的字段)所需要的字节总数。
2.在前面所得字节总数的基础上再加上对象额外的附加成员所需的字节数。
每个对象包括两个附加字段:
一个方法表指针和一个SyncBlockIndex。
3.CLR检查保留区域中的空间是否满足分配新对象所需的字节数如需要则提交物理内存。
如果满足,对象将被分配在NextObjPtr指针所指示的地方。
接着,类型的实例构造器被调用,IL指令newobj返回为其分配的内存地址。
就在newobj指令返回新对象的地址之前,NextObjPtr指针会越过新对象所处的内存区域,并指示出下一个新建对象在托管堆中的地址。
对比C语言运行时中的堆分配内存时的情况。
普通堆中,如果连续地创建几个对象,很可能被分散在地址空间的各个角落。
但在托管堆中,连续分配的对象可以保证它们在内存中也是连续的。
包含3个对象的托管堆,托管堆在实现的简单性和速度方面要优于C语言运行时中的堆。
假设于应用程序的地址空间和存储空间是无限的。
托管堆必须应用某种机制来允许做这样的假设。
这种机制就是垃圾收集器。
垃圾收集器工作原理:
当应用程序调用new操作符创建对象时,托管堆中可能没有足够的地址空间来分配该对象。
托管堆通过将对象所需要的字节总数添加到NextObjPtr指针表示的地址上来检测这种情况。
如果得到的结果超出了托管堆的地址空间范围,那么托管堆将被认为已经充满,这时就需要执行垃圾收集。
每个应用程序都有一组根(root)。
一个根是一个存储位置,其中包含着一个指向引用类型的内存指针。
该指针或者指向一个托管堆中的对象,或者被设为null。
所有全局引用类型变量或静态引用类型变量都被认为是根。
一个线程堆栈上所有引用类型的本地变量或者参数变量也被认为是一个根。
在一个方法内,指向引用类型对象的CPU寄存器也被认为是一个根。
当JIT编译器编译一个方法的IL代码时,除了产生本地CPU代码外,JIT编译器还会创建一个内部的表。
从逻辑上讲,该表中的每一个条目都标识着一个方法的本地CPU指令的字节偏移范围,以及该范围中一组包含根的内存地址。
内部表结构如图:
19.2垃圾收集算法,0x000000000x00000020this,arg1,arg2,ECX,EDX0x000000210x00000122this,arg2,fs,EBX0x000001230x00000145fs,起始字节偏移结尾字节偏移根,如果在0x00000021和0x00000122之间的代码执行时开始了垃圾收集,那么垃圾收集器将知道参数this、参数arg2、本地变量fs以及寄存器EBX都是根,它们引用的托管堆中的对象将不会被认为是可收集的垃圾对象。
除此之外,垃圾收集器还可以遍历线程的调用堆栈,通过检测其中每一个方法的内部表来确定所有调用方法中的根。
最后,垃圾收集器使用其他一些手段来获得存储在全局引用类型变量和静态引用类型变量中保存的根。
JIT编译器生成的表,展示了本地代码偏移和方法中根的映射关系,当垃圾收集器开始执行时,它首先假设托管堆中所有的对象都是可收集的垃圾。
然后,垃圾收集器遍历所有的根,构造出一个包含所有可达对象的图。
例:
上图展示了一个分配有几个对象的托管堆,其中对象A、C、D和F为应用程序的根所直接引用。
所有这些对象都是可达对象图的一部分。
当对象D被添加到该图中时,垃圾收集器注意到它还引用着对象H,于是对象H也被添加到该图中。
垃圾收集器就这样以递归的方式来遍历应用程序中所有的可达对象。
例:
垃圾收集器接着线性地遍历托管堆以寻找包含可收集垃圾对象的连续区块。
如果找到了较大的连续区块,垃圾收集器将会把内存中的一些非垃圾对象移到这些连续区块中以压缩托管堆。
搬移内存中的对象将使所有指向这些对象的指针变得无效,所以垃圾收集器必须修改应用程序的根以使它们指向这些对象更新后的位置。
在托管堆中的内存被压缩之后,托管堆上的NextObjPtr指针将被设为指向最后一个非垃圾对象之后。
垃圾收集执行后的托管堆如下图:
ACDFH,NextObjPtr,根全局变量静态变量本地变量CPU寄存器,垃圾收集执行后的托管堆,两点重要认识:
首先,不必再自己实现代码来管理应用程序中对象的生存期。
其次,前面描述的bug将不复存在。
因为任何不可从应用程序的根中访问的对象都会在某个时刻被收集,所以应用程序不可能再发生内存泄漏的情况。
另外,应用程序也不能再访问已经被释放的对象。
因为如果对象可达,它将不可能被释放;而如果对象不可达,应用程序必将无法访问到它。
下面代码演示了垃圾收集器是怎样分配和管理对象的。
classAppstaticvoidMain()ArrayLista=newArrayList();for(Int32x=0;x10000;x+)a.Add(newObject();Console.WriteLine(a.count);Console.WriteLine(“Endofmethod”);,任何封装了非托管资源的类型,例如:
文件、网络链接、套接字、互斥体等,都必须支持一种称作终止化(finalization)的操作。
终止化操作允许一种资源在它所占用的内存被回收之前首先执行一些清理工作。
要提供终止化操作,必须为类型实现一个名为Finalize的方法。
当垃圾收集器判定一个对象为可收集的垃圾时,它便会调用该对象的Finalize方法(如果存在的话)。
如果一个封装了非托管资源的类型没有定义Finalize方法,那么这些非托管资源将得不到关闭,从而会导致某种程度的资源泄漏(前提是没有显式关闭对象所封装的非托管资源)。
直到进程结束,这些托管资源才会被操作系统回收。
19.3终止化操作,publicsealedclassOSHandleprivateIntPtrhandle;publicOSHandle(IntPtrhandle)this.handle=handle;protectedoverridevoidFinalize()tryCloseHandle(handle);finallybase.Finalize();publicIntPtrToHandle()returnhandle;publicstaticimplicitoperatorIntPtr(OSHandleosHandle)returnosHandle.ToHandle();privateexternstaticBooleanCloseHandle(IntPtrhandle);,定义一个封装着非托管资源的类型,publicsealedclassOSHandleprivateIntPtrhandle;publicOSHandle(IntPtrhandle)this.handle=handle;/当垃圾收集执行时,下面的析构器(Finalize)方法将被/调用,它将关闭非托管资源句柄OSHandle()ColseHandle(handle);publicIntPtrToHandle()returnhandle;publicstaticimplicitoperatorIntPtr(OSHandleosHandle)returnosHandle.ToHandle();privateexternstaticBooleanCloseHandle(IntPtrhandle);,C#为定义Finalize方法提供了特殊的语法:
终止化操作的内部机理:
当应用程序创建一个新对象时,new操作符会为对象从托管堆上分配内存。
如果该对象的类型定义了Finalize方法,那么在该类型的实例构造器运行之前,指向该对象的一个指针将被放到一个称作终止化链表的数据结构里面。
终止化链表是一个由垃圾收集器控制的内部数据结构。
链表上的每一个条目都引用着一个对象,这实际是在告诉垃圾收集器在回收这些对象的内存之前先要调用它们的Finalize方法。
一个包含几个对象的托管堆。
有些是从应用程序的根可达的对象,有些不是。
当对象C、E、F、I和J被创建时,系统会检测到这些对象的类型定义了Finalize方法,于是将指向这些对象的指针添加到终止化链表中。
ABCDEFGHIJ,根全局变量静态变量本地变量CPU寄存器,终止化可达队列,当垃圾收集开始时,对象B、E、G、H、I和J为垃圾对象。
垃圾收集器然后扫描终止化链表以查找其中是否有指向这些对象的指针。
当找到这样的指针时,它们会被从终止化链表中移除,并添加到终止化可达队列。
终止化可达队列中出现的对象表示该对象的Finalize方法即将被调用。
ACDEFIJ,根全局变量静态变量本地变量CPU寄存器,终止化链表,CF,EIJ,终止化可达队列,Finalize方法是.NET内部的一个释放内存资源的方法。
这个方法不对外公开,由垃圾收集器自己调用。
Finalize方法可以确保托管对象在释放内存的同时不会泄漏非托管资源。
问题:
不能确定该方法会在何时被调用,而且由于它并不是一个公有方法,所以也不能显式地调用它。
要提供显式释放或者关闭对象的能力,一个类型通常要实现一种被称为Dispose的模式。
Dispose模式定义了开发人员在实现类型的显式资源清理功能时所要遵循的一些约定。
如果一个类型实现了Dispose模式,使用该类型的开发人员将能够知道当对象不再被使用时如何显式地释放掉它所占用的资源。
Dispose调用方法:
要释放的资源对象.Dispose调用Dispose方法释放对象所封装的非托管资源。
但并不会释放对象在托管堆中占用的内存资源,释放对象内存的工作仍由垃圾收集器负责,而且释放的时间仍不确定。
19.4Dispose模式:
强制对象清理资源,Close方法和Dispose一样,只不过有的对象没有提供Dispose方法,只提供了Close方法,而Close其实在对象的类中,依然是调用了一个私有的Dispose方法。
总结:
.NET中提供了三种模式来回收内存资源:
Dispose模式,Finalize方法、Close方法。
垃圾收集会给应用程序带来不小的性能损伤,CLR的垃圾收集器提供了一些特殊的优化设计来大幅度提高垃圾收集的性能。
代龄是旨在提高垃圾收集器性能的一种机制。
一个基于代龄的垃圾收集器有以下几点假设:
(1)对象越新,其生存期越短。
(2)对象越老,其生存期越长。
(3)对托管堆的一部分执行垃圾收集要比对整个托管堆执行垃圾收集速度更快。
19.5对象的代龄,在托管堆初始化时,其中不包括任何对象。
这时添加到托管堆的对象被称为第0代对象。
简单的说,第0代对象就是那些新构造的对象,垃圾收集器还没有对它们执行过任何检查。
新启动的应用程序中托管堆情况:
分配有5个对象,经一段时间后,对象C和E将变为不可达对象。
当CLR初始化时,它会为第0代对象选择一个阙值容量,假定为256K。
当分配新对象导致第0代对象超过了为其设定的阙值容量时,垃圾收集器就必须启动了。
代龄的工作机制,假设从对象A到E总共占用了256KB,那么当对象F被分配时,垃圾收集器就会启动。
垃圾收集器判定对象C和E为垃圾对象,会压缩对象D使其邻接于对象B。
此次垃圾收集中存活下来的对
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- NET 课件 部分