异常

什么是异常


异常是程序中的运行时错误,它违反了系统约束或应用程序约束,或出现了在正常操作时未预料的情形。例如,程序试图除以0或试图写一个只读文件。当这些发生时,系统捕获这个错误并抛出(raise)一个异常。
如果程序没有提供处理该异常的代码,系统会挂起这个程序。例如,下面的代码在试图用0除一个数时抛出一个异常:

  1. class Program
  2. {
  3. static void Main()
  4. {
  5. int x=10,y=0;
  6. x/=y; //用0除以一个数时抛出一个异常
  7. }
  8. }

当这段代码运行时,系统显示下面的错误信息:

try语句


try语句用来指明为避免出现异常而被保护的代码段,并在发生异常时提供代码处理异常。try语句由3个部分组成,如下图所示。

  • try 块包含为避免出现异常而被保护的代码
  • catch 子句部分含有一个或多个catch子句。这些是处理异常的代码段,它们也称为是异常处理程序
  • finally 块含有在所有情况下都要被执行的代码,无论有没有异常发生

处理异常

前面的示例显示了除以0会导致一个异常。可以修改此程序,把那段代码放在一个try块中,并提供一个简单的catch子句,以处理该异常。当异常发生时,它被捕获并在catch块中处理。

  1. static void Main()
  2. {
  3. int x = 10
  4. try
  5. {
  6. int y=0;
  7. x/=y; //抛出异常
  8. }
  9. catch
  10. {
  11. //异常处理代码
  12. Console.WriteLine("Handling all exceptions - keep on Running");
  13. }
  14. }

这段代码产生以下消息。注意,除了输出消息,没有异常已经发生的迹象。

异常类


有许多不同类型的异常可以在程序中发生。BCL定义了许多类,每一个类代表一个指定的异常类型。当一个异常发生时,CLR:

  • 创建该类型的异常对象
  • 寻找适当的catch子句以处理它

所有异常类都从根本上派生自System.Exception类。异常继承层次的一个部分如下图所示。

异常对象含有只读属性,带有导致该异常的信息。这些属性的其中一些如下表所示。

catch 子句


catch子句处理异常。它有3种形式,允许不同级別的处理。这些形式如下图所示。

一般catch子句能接受任何异常,但不能确定引发异常的类型。这只允许对任何可能发生的异常的普通处理和清理。
特定catch子句形式把一个异常类的名称作为参数。它匹配该指定类或派生自它的异常类的异常。
带对象的特定catch子句提供关于异常的最多信息。它匹配该指定类的异常,或派生自它的异常类的异常。它还给出一个异常实例(称为异常变量),是一个对CLR创建的异常对象的引用。可以在catch子句块内部访问异常变量的属性,以获取关于引起的异常的详细信息。
例如,下面的代码处理IndexOutOfRangeException类型的异常。当异常发生时,一个实际异常对象的引用被参数名e传入代码。那3个WriteLine语句中,每个都从异常对象中读取一个字符串字段。

  1. catch(IndexOutOfRangeException e)
  2. {
  3. Console.WriteLine("Message: {0}",e.Message);
  4. Console.WriteLine("Source: {0}",e.Source);
  5. Console.WriteLine("Stack: {0}",e.StackTrace);
  6. }

使用特定catch子句的示例


回到除以0的示例,下面的代码把前面的catch子句修改为指定处理DivideByZeroException类的异常。在前面的示例中,catch子句会处理所在try块中引起的任何异常,而这个示例将只处理DivideByZeroException类的异常。

  1. int x=10;
  2. try
  3. {
  4. int y=0;
  5. x/=y;
  6. }
  7. catch(DivideByZeroException)
  8. {

  9. Console.WriteLine("Handling an exception.");
  10. }

可以进一步修改catch子句以使用一个异常变量。这允许在catch块内部访问异常对象。

  1. int x=10;
  2. try
  3. {
  4. int y=0;
  5. x/=y;
  6. }
  7. catch(DivideByZeroException e)
  8. {
  9. Console.WriteLine("Message: {0}",e.Message);
  10. Console.WriteLine("Source: {0}",e.Source);
  11. Console.WriteLine("Stack: {0}",e.StackTrace);
  12. }

在笔者的电脑上,这段代码会产生以下输出。对于读者的机器,第三行和第四行的代码路径可能不同,这要与你的项目位置和解决方案目录匹配。

catch子句段


catch子句的目的是允许你以一种优雅的方式处理异常。如果你的catch子句接受一个参数,那么系统会把这个异常变量设置为异常对象,这样你就可以检査并确定异常的原因。如果异常是前一个异常引起的,你可以通过异常变量的InnerException属性来获得对前一个异常对象的引用。catch子句段可以包含多个catch子句。下图显示了catch子句段。
当异常发生时,系统按顺序搜索catch子句的列表,第一个匹配该异常对象类型的catch子句被执行。因此,catch子句的排序有两个重要的规则。具体如下。

  • 特定catch子句必须以一种顺序排列,最明确的异常类型第一,直到最普通的类型。例如,如果声明了一个派生自NullReferenceException的异常类,那么派生异常类型的catch子句应该被列在NullReferenceException的catch子句之前
  • 如果有一个一般catch子句,它必须是最后一个,并且在所有特定catch子句之后。不鼓励使用一般catch子句.因为它允许程序继续执行隐藏错误,让程序处于一种未知的状态。应尽可能使用特定catch子句

finally块


如果程序的控制流进人了一个带finally块的try语句,那么finally始终会被执行。下图阐明了它的控制流。

  • 如果在try块内部没有异常发生,那么在try块的结尾,控制流跳过任何catch子句并到finally块
  • 如果在try块内部发生了异常,那么在catch子句段中无论哪一个适当的catch子句被执行,接着就是finally块的执行


即使try块中有return语句或在catch块中抛出一个异常,finally块也总是会在返回到调用代码之前执行。例如,在下面的代码中,在try块的中间有一条return语句,它在某条件下被执行。
这不会使它绕过finally语句。

  1. try
  2. {
  3. if(inVal<10)
  4. {
  5. Console.Write("First Branch - ");
  6. return;
  7. }
  8. else
  9. {
  10. Console.Write("Second Branch - ");
  11. }
  12. }
  13. finally
  14. {
  15. Console.WriteLine("In finally statement");
  16. }

这段代码在inVal值为5时产生以下输出:

为异常寻找处理程序


当程序产生一个异常时,系统查看该程序是否为它提供了一个处理代码。下图阐明了这个控制流。

  • 如果在try块内发生了异常,系统会査看是否有任何一个catch子句能处理该异常
  • 如果找到了适当的catch子句,以下3项中的1项会发生
    • 该catch子句被执行
    • 如果有finally块,那么它被执行
    • 执行在try语句的尾部继续(也就是说,在finally块之后,或如果没有finally块,就在最后一个catch子句之后)

更进一步搜索


如果异常在一个没有被try语句保护的代码段中产生,或如果try语句没有匹配的异常处理程序,系统将不得不更进一步寻找匹配的处理代码。为此它会按顺序搜索调用栈,以看看是否存在带匹配的处理程序的封装try块。
下图阐明了这个搜索过程。图左边是代码的调用结构,右边是调用栈。该图显示Method2被从Method1的try块内部调用。如果异常发生在Method2内的try块内部,系统会执行以下操作。

  • 首先,它査看Method2是否有能处理该异常的异常处理程序

    • 如果有,Method2处理它,程序继续执行
    • 如果没有,系统再延着调用栈找到Method1,搜寻一个适当的处理程序
  • 如果Method1有一个适当的catch子句,那么系统将:
    • 回到栈顶,那里是Method2
    • 执行Method2的finally块,并把Method2弹出栈
    • 执行Method1的catch子句和它的finally块
  • 如果Method1没有适当的catch子句,系统会继续搜索调用栈。

一般法则

下图展示了处理异常的一般法则。

搜索调用栈的示例

在下面的代码中,Main开始执行并调用方法A,A调用方法B。代码之后给出了相应的说明, 并在图22-9中再现了整个过程。

  1. class Program
  2. {
  3. static void Main()
  4. {
  5. var MCls=new MyClass();
  6. try
  7. {
  8. MCls.A();
  9. }
  10. catch(DivideByZeroException e)
  11. {
  12. Console.WriteLine("catch clause in Main()");
  13. }
  14. finally
  15. {
  16. Console.WriteLine("finally clause in Main()");
  17. }
  18. Console.WriteLine("After try statement in Main.");
  19. Console.WriteLine(" -- keep running.");
  20. }
  21. }
  22. class MyClass
  23. {
  24. public void A()
  25. {
  26. try
  27. {
  28. B();
  29. }
  30. catch(System.NullReferenceException)
  31. {
  32. Console.WriteLine("catch clause in A()");
  33. }
  34. finally
  35. {
  36. Console.WriteLine("finally clause in A()");
  37. }
  38. }
  39. void B()
  40. {
  41. int x=10,y=0;
  42. try
  43. {
  44. x/=y;
  45. }
  46. catch(System.IndexOutOfRangeException)
  47. {
  48. Console.WriteLine("catch clause in B()");
  49. }
  50. finally
  51. {
  52. Console.WriteLine("finally clause in B()");
  53. }
  54. }
  55. }

这段代码产生以下输出:

  1. Main调用A,A调用B,B遇到一个DivideByZeroException异常
  2. 系统检查B的catch段寻找匹配的catch子句。虽然它有一个IndexOutOfRangeException的子句,但没有DivideByZeroException的
  3. 系统然后延着调用栈向下移动并检査A的catch段,在那里它发现A也没有匹配的catch子句
  4. 系统继续延调用栈向下,并检查Main的catch子句部分,在那里它发现Main确实有一个DivideByZeroException的catch子句
  5. 尽管匹配的catch子句现在被定位了,但并不执行。相反,系统回到栈的顶端,执行B的finally子句,并把B从调用栈中弹出
  6. 系统移动到A,执行它的finally子句,并把A从调用栈中弹出
  7. 最后,Main的匹配catch子句被执行,接着是它的finally子句。然后执行在Main的try语句结尾之后继续

抛出异常


可以使用throw语句使代码显式地引发一个异常。throw语句的语法如下:
throw ExceptionObject;
例如,下面的代码定义了一个名称为PrintArg的方法,它带一个string参数并把它打印出来。在try块内部,它首先做检査以确认该参数不是null。如果是null,它创建一个ArgumentNullException实例并抛出它。该异常实例在catch语句中被捕获,并且该出错消息被打印。Main调用该方法两次:一次用null参数,然后用一个有效参数。

  1. class MyClass
  2. {
  3. public static void PrintArg(string arg)
  4. {
  5. try
  6. {
  7. if(arg==null)
  8. {
  9. var myEx=new ArgumentNullException("arg");
  10. throw myEx;
  11. }
  12. Console.WriteLine(arg);
  13. }
  14. catch(ArgumentNullException e)
  15. {
  16. Console.WriteLine("Message: {0}",e.Message);
  17. }
  18. }
  19. }
  20. class Program
  21. {
  22. static void Main()
  23. {
  24. string s=null;
  25. MyClass.PrintArg(s);
  26. MyClass.PrintArg("Hi there!");
  27. }
  28. }

这段代码产生以下输出:

不带异常对象的抛出


throw语句还可以不带异常对象使用,在catch块内部。

  • 这种形式重新抛出当前异常,系统继续它的搜索,为该异常寻找另外的处理代码
  • 这种形式只能用在catch语句内部

例如,下面的代码从第一个catch子句内部重新抛出异常:

  1. class MyClass
  2. {
  3. public static void PrintArg(string arg)
  4. {
  5. try
  6. {
  7. try
  8. {
  9. if(arg==null)
  10. {
  11. var myEx=new ArgumentNullException("arg");
  12. throw myEx;
  13. }
  14. Console.WriteLine(arg);
  15. }
  16. catch(ArgumentNullException e)
  17. {
  18. Console.WriteLine("Inner Catch: {0}",e.Message);
  19. throw;
  20. }
  21. }
  22. catch
  23. {
  24. Console.WriteLine("Outer Catch: Handling an Exception.");
  25. }
  26. }
  27. }
  28. class Program
  29. {
  30. static void Main()
  31. {
  32. string s=null;
  33. MyClass.PrintArg(s);
  34. }
  35. }

这段代码产生以下输出:

<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">

 
 
 
 

C#图解教程 第二十二章 异常的更多相关文章

  1. python 教程 第二十二章、 其它应用

    第二十二章. 其它应用 1)    Web服务 ##代码 s 000063.SZ ##开盘 o 26.60 ##最高 h 27.05 ##最低 g 26.52 ##最新 l1 26.66 ##涨跌 c ...

  2. Flask 教程 第二十二章:后台作业

    本文翻译自The Flask Mega-Tutorial Part XXII: Background Jobs 这是Flask Mega-Tutorial系列的第二十二部分,我将告诉你如何创建独立于W ...

  3. C#图解教程 第二十五章 其他主题

    其他主题 概述字符串使用 StringBuilder类把字符串解析为数据值关于可空类型的更多内容 为可空类型赋值使用空接合运算符使用可空用户自定义类型 Main 方法文档注释 插入文档注释使用其他XM ...

  4. C#图解教程 第二十四章 反射和特性

    反射和特性 元数据和反射Type 类获取Type对象什么是特性应用特性预定义的保留的特性 Obsolete(废弃)特性Conditional特性调用者信息特性DebuggerStepThrough 特 ...

  5. C#图解教程 第十二章 数组

    数组 数组 定义重要细节 数组的类型数组是对象一维数组和矩形数组实例化一维数组或矩形数组访问数组元素初始化数组 显式初始化一维数组显式初始化矩形数组快捷语法隐式类型数组综合内容 交错数组 声明交错数组 ...

  6. “全栈2019”Java异常第二十二章:try-with-resources语句详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异 ...

  7. 《Linux命令行与shell脚本编程大全》 第二十二章 学习笔记

    第二十二章:使用其他shell 什么是dash shell Debian的dash shell是ash shell的直系后代,ash shell是Unix系统上原来地Bourne shell的简化版本 ...

  8. 第二十二章 Django会话与表单验证

    第二十二章 Django会话与表单验证 第一课 模板回顾 1.基本操作 def func(req): return render(req,'index.html',{'val':[1,2,3...]} ...

  9. Gradle 1.12用户指南翻译——第二十二章. 标准的 Gradle 插件

    其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ...

随机推荐

  1. 在ASP.NET MVC中使用Web API和EntityFramework构建应用程序

    最近做了一个项目技术预研:在ASP.NET MVC框架中使用Web API和EntityFramework,构建一个基础的架构,并在此基础上实现基本的CRUD应用. 以下是详细的步骤. 第一步 在数据 ...

  2. C#查询XML解决“需要命名空间管理器”问题

    在查询xml时有时会遇到带有前缀的xml,例如:"<ows:Keyword></ows:Keyword>" 这时像往常一样查询就会报错,类似于"需 ...

  3. Promise对象的简单用法

    要了解一个东西,首先要从,它是什么.用来做什么以及怎么取用它这三个方面来了解. 首先,promise是什么? 我们来参考一下MDN对它的定义: Promise 对象用于一个异步操作的最终完成(或失败) ...

  4. ionic2+Angular web端 实现微信分享以及如何跳转回分享出去的页面

    微信分享,首先参考微信JS-SDK开发文档. step1:在启动文件index.html中引入微信js文件: <script src="http://res.wx.qq.com/ope ...

  5. PHP输出打印方法

    PHP这门语言灵活而充满众多的API和用法,然而在这个技术领域里却缺乏一些系统的总结归纳.或许这与PHP语言的诞生方式有关,衍生,快速变化,原始限制等等,诸多因素决定这门语言变得smarty,却没有人 ...

  6. phpstorm使用之——常用快捷键

    phpstorm使用之--常用快捷键 使用IDE的根本所在乃是为了提高工作效率. windows下phpstorm的快捷键 ctrl+shift+n查找文件 ctrl+shift+f 在一个目录里查找 ...

  7. Git 如何 clone 非 master 分支的代码

    问题描述 我们每次使用命令 git clone git@gitlab.xxx.com:xxxxx.git 默认 clone 的是这个仓库的 master 分支.如果最新的代码不在 master 分支上 ...

  8. maven使用jstl表达式和The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application解决

    maven 中使用jstl表达式中出现如上错误.原因: 1.由于在maven中的web项目没有自动依赖jstl的jar 未在pom文件中添加jstl相关的jar <!--jstl表达式--> ...

  9. Yii2-admin的详细使用教程

    Yii2-admin的详细使用教程 参考:http://www.yiichina.com/tutorial/571    http://www.kancloud.cn/curder/yii/24775 ...

  10. C语言_来了解一下GCC编译器编译C可执行脚本的过程

    GCC简介    Linux系统下的gcc(GNU C Compiler)是GNU推出的功能强大.性能优越的多平台编译器,是GNU的代表作品之一.gcc是可以在多种硬体平台上编译出可执行程序的超级编译 ...