C# 知识回顾 - 你真的懂异常(Exception)吗?

你真的懂异常(Exception)吗?

目录

  • 异常介绍
  • 异常的特点
  • 怎样使用异常
  • 处理异常的 try-catch-finally
    • 捕获异常的 Catch 块
    • 释放资源的 Finally 块

一、异常介绍

  我们平时在写程序时,无意中(或技术不够),而导致程序运行时出现意外(或异常),对于这个问题, C# 有专门的异常处理程序。 异常处理所涉及到的关键字有 trycatch 和 finally 等,用来处理失败的情况。 CLR、.NET 自身的类库、其它第三方库或者你写的程序代码都有可能会出现异常。当然,你也可以直接使用 throw ,通过显式的形式来进行创建异常。

  在你的代码中出现异常的时候,程序会找到并执行最先匹配的 catch 块。 如果在调用堆栈中的任意位置中,异常处理程序都没有找到合适(你写的)的 catch 块,就会自动终止该进程,并向用户显示(抛出)一条错误的信息。

  在这里我写了个被 0 除会出现的异常(一个显式引发 DivideByZeroException 异常)并捕获该异常的示例:

 1         /// <summary>
 2         /// 除法
 3         /// </summary>
 4         /// <param name="x"></param>
 5         /// <param name="y"></param>
 6         /// <returns></returns>
 7         static double Division(double x, double y)
 8         {
 9             if (y == 0)
10             {
11                 throw new DivideByZeroException();
12             }
13
14             return x / y;
15         }
16
17         static void Main(string[] args)
18         {
19             //定义两个变量 x, y
20             double x = 250, y = 0;
21
22             try
23             {
24                 var result = Division(x, y);
25                 Console.WriteLine($"result: {result}");
26             }
27             catch (DivideByZeroException e)
28             {
29
30                 Console.WriteLine(e);
31             }
32
33             Console.Read();
34         }

二、异常的特点

  • 所有异常类型(包括自定义的异常)都是由基类 Exception 派生的。
  • 使用 try 块包围你认为可能会出现异常的代码。
  • 一旦 try 块中发生异常,控制流将按顺序找到与之关联的 catch 块,如果没有找到合适的,就会引发最终的异常基类 Exception 内的处理程序(前提你已经 catch)。
  • 如果出现异常却没有对应的异常处理程序,则该程序将会停止执行,并抛出对应错误的信息。
  • 在 catch 定义了的异常变量,可以获取对应异常类型的信息。比如调用堆栈的状态和错误的说明,具体看 Excetion 的属性。
  • throw 关键字可以显式引发异常。
  • 即使出现异常也会执行 finally 块中的代码。一般来说,我们会使用 finally 块释放资源,例如,关闭xx流。

三、怎样使用异常

  程序在运行时出现的错误,会不断在程序中进行传播,这种机制称为“异常”。 异常通常由错误的代码引发,并由能够更正错误的代码进行 catch。 异常也可以由 .NET 的 CLR 或由程序中的代码引发, 一旦引发了异常,这个异常将会在调用堆栈中一直向上进行传播,直到寻找到跟它匹配的 catch 语句。没有 catch 的异常会由系统提供的默认的异常处理程序进行处理,也就是你经常看到的一个突然造成调试中断并显示异常信息的对话框。

  所有的异常,它们都是从 Exception 派生出来的。这些异常的类型,都会包含详细描述异常的属性。在这里我将自定义了一个新的异常类,其实也可以自定义配置异常的属性(这是可选的),然后我使用 throw 关键字显示引发该对象(即异常)。

 1         /// <summary>
 2         /// 定义新异常
 3         /// </summary>
 4         class MyException : Exception
 5         {
 6             public MyException(string msg) { }
 7         }
 8
 9         /// <summary>
10         /// 抛出新定义的异常
11         /// </summary>
12         static void ThrowMyExcetion()
13         {
14             throw new MyException("Sorry, this is test!");
15         }

  在引发异常之后,运行时程序会检查当前语句确定它是否包含在 try 块中。 如果是的话,就会检查与该 try 块相关联的所有 catch 块,来确定它们是否能够 catch 该异常。  catch 块通常会指定异常类型;如果该 catch 块的类型与异常或它的基类的相同(或匹配),则该 catch 块就能够捕获并处理。

 1         static void Main(string[] args)
 2         {
 3             try
 4             {
 5                 ThrowMyExcetion();  //直接调用抛出异常的方法
 6             }
 7             catch (MyException e)
 8             {
 9                 Console.WriteLine(e);
10             }
11
12             Console.Read();
13         }

  CLR 会检查调用方法中是否有 try 块和 catch 块,并将在调用堆栈中继续往上搜索兼容(或匹配)的 catch 块。在找到并执行 catch 块之后,控制权将传递给 catch 块之后的下一个语句。

  一个 try 语句可能包含多个 catch 块。 程序将执行第一个能够处理该异常的 catch 语句;任何后续的 catch 语句都将被忽略,即使它们是兼容的也如此。 因此,在任何情况下都应该按照从最具体(或者派生程度最高)到最不具体这一顺序排列 catch 块。 例如:

 1         static void Main(string[] args)
 2         {
 3             StreamWriter sw = null;
 4
 5             try
 6             {
 7                 sw = new StreamWriter(@"C:\book\小二和小三的故事.txt");
 8                 sw.Write("You are 250.");
 9             }
10             catch (FileNotFoundException e)
11             {
12                 //将具体的异常放在第一位
13                 Console.WriteLine(e);
14             }
15             catch (IOException e)
16             {
17                 //将并不具体的放在相对后面的位置
18                 Console.WriteLine(e);
19             }
20             catch (Exception e)
21             {
22                 Console.WriteLine(e);
23             }
24             finally
25             {
26                 if (sw != null)
27                 {
28                     sw.Close();
29                 }
30             }
31
32             Console.Read();
33         }

  执行 catch 块之前,CLR 会检查 finally 块。 finally 块使程序员能够清除中止的 try 块可能遗留下的任何模糊状态,或者释放任何外部资源(例如图形句柄、db 连接或 IO 流),而无需等待 CLR 中的垃圾回收器终结这些对象。 例如:

 1         static void Main(string[] args)
 2         {
 3             FileStream fs = null;
 4             FileInfo fi = new FileInfo(@"小二和小三的故事.txt");
 5
 6             try
 7             {
 8                 fs = fi.OpenWrite();
 9                 fs.WriteByte(0);
10             }
11             finally
12             {
13                 //记住哦,如果你忘记 close,将会引发 IO 异常!
14                 //if (fs != null)
15                 //{
16                 //    fs.Close();
17                 //}
18             }
19
20             try
21             {
22                 fs = fi.OpenWrite();
23                 fs.WriteByte(1);
24                 Console.WriteLine("OK!");
25             }
26             catch (IOException e)
27             {
28                 Console.WriteLine("Fail!");
29             }
30
31             Console.Read();
32         }

  你看到结果了吗,是:“Fail!”,这是因为上面注释了需要关闭 IO 流的语句,你可以尝试下去掉注释再看看结果,记住哦,IO 操作都应该在结束时释放资源。 

  如果 WriteByte(0)(第9行) 引发了异常,那么在没有调用 fs.Close() 的情况下,你在第二个 try 块中尝试重新 OpenWrit() 的代码就会失败,因为此时文件会保持锁定状态。 假如你取消注释,由于会执行 finally 块(即使已引发异常),使得可以正确地关闭文件,从而避免再次引发异常。

  如果在引发异常之后没有在调用堆栈上找到相匹配的 catch 块,则会可能会出现下面的情况:

  • 如果异常出现在析构函数中,则中止该析构函数并调用基类的析构函数(如果有)。
  • 如果调用堆栈包含静态构造函数或静态字段初始值设定项,则会引发 TypeInitializationException,并将原始异常分配给新异常的 InnerException 属性。
  • 如果到达线程的开头,将会终止线程。

四、处理异常的 try-catch-finally

  你可以使用 try 块来对你觉得可能会出现异常的代码进行分区。 其中,与之关联的 catch 块可用于处理任何异常情况。 一个包含代码的 finally 块,无论 try 块中是否在运行时引发异常(例如,释放在 try 块中分配的资源),这些 finally 块的代码都会运行。 这些“异常部分”:可以由一个 try 块、一个或多个关联的 catch 块、一个 finally 块分别组合。

  这里我列举了 3 种情况:一个 try-catch 语句,一个 try-finally 语句,和一个 try-catch-finally 语句。

  (1)try-catch:

 1         static void Main(string[] args)
 2         {
 3             try
 4             {
 5                 //需要执行的代码
 6             }
 7             catch (Exception e)
 8             {
 9                 //这里可以获取到被捕获的异常
10                 //你需要知道自己应该如何处理该异常
11             }
12         }

  (2)try-finally:

1             try
2             {
3                 //需要执行的代码
4             }
5             finally
6             {
7                 //在 try 块后执行的代码
8             }

  (3)try-catch-finally:

 1             try
 2             {
 3                 //需要执行的代码
 4             }
 5             catch (Exception e)
 6             {
 7                 //这里处理异常
 8             }
 9             finally
10             {
11                 //在 try 块(也可能是 catch 块)后执行的代码
12             }

  【备注】不带有 catch 或 finally 块的 try 块将导致编译器错误。

4.1 捕获异常的 Catch 块

  catch 块可以指定要捕捉的异常类型,又可以称为“异常筛选器”。 异常类型都是从 Exception 派生出来。 一般而言,不会将所有异常的基类 System.Exception 指定为要 catch 的“异常筛选器”,除非你非常了解如何处理由 try 块引发的所有异常,或者在 catch 块中包括了 throw 语句。

  多个 catch 块可以串联在一起(要求异常筛选器不同)。 多个 catch 块的执行顺序是:在代码中,从顶部到底部,但是,对于在运行时所引发的每一个异常,程序都只会执行一个 catch 数据块。 与指定的异常类型或它的基类相匹配的第一个 catch 块,才会被执行。 通常,我们需要将最特殊(最具体或者说派生程度最最最高)的异常类,这段 catch 块放在所有 catch 块的最前面,而他们的基类 Excetion 的 catch 块就放在最后(当然,也可以不写)。

  在以下条件为真时,你应该选择 catch 异常:

  • 了解引发异常的原因,并可实现有选择性的恢复。例如,在捕获 FileNotFoundException 时你可以提示用户“文件找不到”和“请输入新的文件名”等。
  • 你也可以新建一个更具体或者说更具有代表性的异常,并选择引发该异常。

 1         double GetNum(double[] nums,int index)
 2         {
 3             try
 4             {
 5                 return nums[index];
 6             }
 7             catch (IndexOutOfRangeException e)
 8             {
 9                 throw new ArgumentOutOfRangeException("Sorry, 你想要的索引已经超出界限!");
10             }
11         }

  

  希望在将异常抛出去时,我们通常会选择处理部分异常。 在下面这个示例中,catch 块在再次 throw 异常之前,添加错误日志。

 1             try
 2             {
 3                 //尝试访问系统资源
 4             }
 5             catch (Exception e)
 6             {
 7                 //伪代码:记录错误日志
 8                 log.Error(e);
 9
10                 //再重新抛出错误
11                 throw;
12             }

4.2 释放资源的 Finally 块

  可以使用 finally 块释放(清理)在 try 块中需要执行释放(清理)资源的操作。 如果存在finally 块,它将在最后执行,也就是在 try 块和任何匹配 catch 块之后执行。 不管是否引发异常或者说是否找到与异常类型相匹配的 catch 块,finally 块它始终都会运行。

  可以使用 finally 块释放资源(如 IO 流、DB 连接和图形句柄),而不要等待运行时中的垃圾回收器来完成对象资源的回收。 其实,我们更建议使用 using 语句。

  在下面的示例中,我使用 finally 块关闭在 try 块中打开的文件。注意,在关闭文件之前你应该要检查该文件句柄的状态。 如果 try 块无法打开文件,则文件句柄的值依然为 null,这时, finally 块就不会尝试关闭它。 或者说,如果在 try 块中成功打开该文件,则 finally 块才会成功地关闭正在打开的文件。

 1         static void Main(string[] args)
 2         {
 3             FileStream fs = null;
 4             FileInfo fi = new System.IO.FileInfo("C:\\小二和小三的故事.txt");
 5
 6             try
 7             {
 8                 fs = fi.OpenWrite();
 9                 fs.WriteByte(0);
10             }
11             finally
12             {
13                 // 记得判断 null 哦,不然可能触发其它异常
14                 if (fs != null)
15                 {
16                     fs.Close();
17                 }
18             }
19
20         }

时间: 2024-12-15 01:36:53

C# 知识回顾 - 你真的懂异常(Exception)吗?的相关文章

[C#] C# 知识回顾 - Excetion 异常的介绍、使用和处理

Excetion 异常的介绍.使用和处理 目录 异常介绍 异常的特点 怎样使用异常 处理异常的 try-catch-finally 捕获异常的 Catch 块 释放资源的 Finally 块 一.异常介绍 我们平时在写程序时,无意中(或技术不够),而导致程序运行时出现意外(或异常),对于这个问题, C# 有专门的异常处理程序. 异常处理所涉及到的关键字有 try.catch 和 finally 等,用来处理失败的情况. CLR..NET 自身的类库.其它第三方库或者你写的程序代码都有可能会出现异

[C#] C# 知识回顾 - 异常介绍

异常介绍 C# 语言的异常处理功能可以帮助你在处理程序运行时出现的意外或异常情况. 异常处理使用 try.catch 和 finally 等关键字尝试执行某些操作,以处理失败情况,当然,尽管这些操作也有可能失败,如果你确定需要这样做,且希望在事后清理资源,就可以尝试这样做. 公共语言运行时 (CLR)..NET 或任何第三方库或者应用程序代码都有可能会生成异常. 异常是你可以使用 throw 关键字显式进行创建. 很多情况下,异常很可能不是由代码直接调用的方法引发,而是由调用堆栈中位置更靠下的另

Java知识回顾 (11) 异常处理

距离最近的 Java知识回顾系列(10),2019.4.24日,到现在,已经近半年过去了. 感觉,做一件事情,如果有头无尾,实在不好,心里会一直悬着.所以,现在继续上面的内容. 再次声明,正如(1)中所描述的,本资料来自于runoob,略有修改. 异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的. 比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error. 异常发生的原因有很多,通常包含以下几大类: 用户输入了非法数据. 要打开的文

你真的懂软件测试吗?

所谓金山银四,又是一波求职月,不安的因素在悸动.测试行业也是如此,作为软件测试员的我也在寻求更好的职业机会,软件测试岗同时也在做筛选,所谓优胜劣汰. 那么面临跳槽季,想在测试行业大展身手的你,真的懂软件测试嘛?小黑板,划重点~ 1.基础知识掌握 这部分,属于对自身的基础能力考查.也是进入测试行业的标准,包括:软件测试原理.软件测试的测试方法了解(刚入行,先了解起来).掌握常见的测试工具(如:UI自动化测试工具TestWriter.开源测试工具QTP.selenium等)等. 2.测试流程掌握 新

java基础知识回顾之java Thread类学习(八)--java多线程通信等待唤醒机制经典应用(生产者消费者)

 *java多线程--等待唤醒机制:经典的体现"生产者和消费者模型 *对于此模型,应该明确以下几点: *1.生产者仅仅在仓库未满的时候生产,仓库满了则停止生产. *2.消费者仅仅在有产品的时候才能消费,仓空则等待. *3.当消费者发现仓储没有产品可消费的时候,会唤醒等待生产者生产. *4.生产者在生产出可以消费的产品的时候,应该通知等待的消费者去消费. 下面先介绍个简单的生产者消费者例子:本例只适用于两个线程,一个线程生产,一个线程负责消费. 生产一个资源,就得消费一个资源. 代码如下: pub

JS基础知识回顾:引用类型(一)

在ECMAScript中引用类型是一种数据结构,用于将数据和功能组织在一起,而对象时引用类型的一个实例. 尽管ECMAScript从技术上讲是一门面向对象的语言,但它不具备传统的面向对象语言所支持的类和接口等基本结构,所以虽然说引用类型与类看起来想死,但他们并不是相同的概念. 不过引用类型有的时候也可以被称为对象定义,因为他们描述的是一类对象所具有的属性和方法. 新对象是使用new操作符后跟一个构造函数来实现的,构造函数本身就是一个函数,只不过该函数时处于创建新对象的目的而定义的. ECMASc

浅谈java异常[Exception]

本文转自:focusJ 一. 异常的定义 在<java编程思想>中这样定义 异常:阻止当前方法或作用域继续执行的问题.虽然java中有异常处理机制,但是要明确一点,决不应该用"正常"的态度来看待异常.绝对一点说异常就是某种意义上的错误,就是问题,它可能会导致程序失败.之所以java要提出异常处理机制,就是要告诉开发人员,你的程序出现了不正常的情况,请注意. 记得当初学习java的时候,异常总是搞不太清楚,不知道这个异常是什么意思,为什么会有这个机制?但是随着知识的积累逐渐也

java基础知识回顾之---java String final类普通方法

辞职了,最近一段时间在找工作,把在大二的时候学习java基础知识回顾下,拿出来跟大家分享,如果有问题,欢迎大家的指正. /*     * 按照面向对象的思想对字符串进行功能分类.     *      *      * 1,获取:     * 1.1 获取字符串中字符的个数(长度).     *         int length();     * 1.2 取字符串中的某一个字符,其中的参数index指的是字符串中序数.字符串的序数从0开始到length()-1 .     *       

python爬虫主要就是五个模块:爬虫启动入口模块,URL管理器存放已经爬虫的URL和待爬虫URL列表,html下载器,html解析器,html输出器 同时可以掌握到urllib2的使用、bs4(BeautifulSoup)页面解析器、re正则表达式、urlparse、python基础知识回顾(set集合操作)等相关内容。

本次python爬虫百步百科,里面详细分析了爬虫的步骤,对每一步代码都有详细的注释说明,可通过本案例掌握python爬虫的特点: 1.爬虫调度入口(crawler_main.py) # coding:utf-8from com.wenhy.crawler_baidu_baike import url_manager, html_downloader, html_parser, html_outputer print "爬虫百度百科调度入口" # 创建爬虫类class SpiderMai