异常

什么是异常


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

class Program
{
static void Main()
{
int x=10,y=0;
x/=y; //用0除以一个数时抛出一个异常
}
}

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

try语句


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

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

处理异常

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

static void Main()
{
int x = 10;
try
{
int y=0;
x/=y; //抛出异常
}
catch
{
…//异常处理代码
Console.WriteLine("Handling all exceptions - keep on Running");
}
}

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

异常类


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

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

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

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

catch 子句


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

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

catch(IndexOutOfRangeException e)
{
Console.WriteLine("Message: {0}",e.Message);
Console.WriteLine("Source: {0}",e.Source);
Console.WriteLine("Stack: {0}",e.StackTrace);
}

使用特定catch子句的示例


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

int x=10;
try
{
int y=0;
x/=y;
}
catch(DivideByZeroException)
{

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

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

int x=10;
try
{
int y=0;
x/=y;
}
catch(DivideByZeroException e)
{
Console.WriteLine("Message: {0}",e.Message);
Console.WriteLine("Source: {0}",e.Source);
Console.WriteLine("Stack: {0}",e.StackTrace);
}

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

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语句。

try
{
if(inVal<10)
{
Console.Write("First Branch - ");
return;
}
else
{
Console.Write("Second Branch - ");
}
}
finally
{
Console.WriteLine("In finally statement");
}

这段代码在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中再现了整个过程。

class Program
{
static void Main()
{
var MCls=new MyClass();
try
{
MCls.A();
}
catch(DivideByZeroException e)
{
Console.WriteLine("catch clause in Main()");
}
finally
{
Console.WriteLine("finally clause in Main()");
}
Console.WriteLine("After try statement in Main.");
Console.WriteLine(" -- keep running.");
}
}
class MyClass
{
public void A()
{
try
{
B();
}
catch(System.NullReferenceException)
{
Console.WriteLine("catch clause in A()");
}
finally
{
Console.WriteLine("finally clause in A()");
}
}
void B()
{
int x=10,y=0;
try
{
x/=y;
}
catch(System.IndexOutOfRangeException)
{
Console.WriteLine("catch clause in B()");
}
finally
{
Console.WriteLine("finally clause in B()");
}
}
}

这段代码产生以下输出:

  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参数,然后用一个有效参数。

class MyClass
{
public static void PrintArg(string arg)
{
try
{
if(arg==null)
{
var myEx=new ArgumentNullException("arg");
throw myEx;
}
Console.WriteLine(arg);
}
catch(ArgumentNullException e)
{
Console.WriteLine("Message: {0}",e.Message);
}
}
}
class Program
{
static void Main()
{
string s=null;
MyClass.PrintArg(s);
MyClass.PrintArg("Hi there!");
}
}

这段代码产生以下输出:

不带异常对象的抛出


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

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

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

class MyClass
{
public static void PrintArg(string arg)
{
try
{
try
{
if(arg==null)
{
var myEx=new ArgumentNullException("arg");
throw myEx;
}
Console.WriteLine(arg);
}
catch(ArgumentNullException e)
{
Console.WriteLine("Inner Catch: {0}",e.Message);
throw;
}
}
catch
{
Console.WriteLine("Outer Catch: Handling an Exception.");
}
}
}
class Program
{
static void Main()
{
string s=null;
MyClass.PrintArg(s);
}
}

这段代码产生以下输出:

<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. 创建hbase-indexer出现 0 running

    新建hbase-indexer后通过hbase-indexer list-indexers发现SEP subscription ID: null并且0 running processes,如下: IN ...

  2. 模板方法模式和JDBCTemplate(一)

    本篇博客的目录: 一:模板方法模式介绍 二:模板方法模式的简单实现 三:总结 一:模板方法模式的介绍 1.1:模板方法模式的定义 定义:一个操作中的算法的骨架,而将一些步骤延迟到子类中.Templat ...

  3. CentOS6搭建OpenVPN服务器

    一.服务器端安装及配置 服务器环境:干净的CentOS6.3 64位系统 内网IP:10.143.80.116 外网IP:203.195.xxx.xxx OpenVPN版本:OpenVPN 2.3.2 ...

  4. 接口-以PHP为例

    <?php //使用程序模拟现实情况 //使用规范(方法/属性) interface iUSB { public function start(); public function stop() ...

  5. Angular CurrencyPipe货币管道关于人民币符号¥的问题

    做项目(Angular项目)时经常需要处理金额的显示,需要在金额前面加上¥,但又不想用简单在前面加"¥"这么不优雅的方式,于是想到了CurrencyPipe.毕竟,Currency ...

  6. Qt 5.9.4 如何静态编译和部署?

    Qt 5.9.4 如何静态编译和部署? MSVC2015 x86 静态编译 Qt 部署静态库 VS2015 部署静态库 1. MSVC2015 x86 静态编译 1.1 Qt 官网下载最新源代码 立即 ...

  7. Qt 网络模块如何使用?

    1.网络模块介绍 类名 说明 中文 QAbstractNetworkCache The interface for cache implementations 缓存实现的接口 QNetworkCach ...

  8. hexo持续更新记录

    port:50187

  9. Storm实践

    1.Storm命令 在Linux中观直接输入Storm,不带任何参数信息,可以查看Storm命令. 参考这里 1.  activate 激活指定的拓扑Spout.语法:storm activate t ...

  10. 文件无法复制的原因-IT33

    Win7系统复制数据至其他硬盘或者是移动存储设备是,有时会发生无法复制文件过大的情况.这里先大致介绍一下硬盘文件系统分为NFTS格式和FAT32格式这两种,其中FAT32仅支持单次移动4G以下容量的数 ...