你真的懂异常(Exception)吗?

目录

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

一、异常介绍

  我们平时在写代码或程序时,无意中(一般就是技术不够),而导致程序运行时出现意外(又称为异常),对于这个问题, C# 有专门的异常处理程序(当然其他语言也有)。

  异常处理所涉及到的关键字有几个,不用说都知道,已经耳熟能详了:trycatch 和 finally 等,用来处理失败的情况。当然,尽管这些操作也有可能失败,一般来说是释放,清理某些资源或记录日志等。

  哪些代码会出现异常呢:使用的基类库 BCL、第三方类库和我们写的自以为是的代码,还有,可以使用 throw 显式抛出异常。

  一种普遍出现的情形,异常很可能不是由代码直接引发,而是由调用堆栈中更靠下的位置其它方法所引发。在这种情形下下,CLR 会展开堆栈,并查找是否有包含针对你指定异常类型的 catch 块的代码,如果找到的话,就会执行最先匹配的 catch 块。 如果在调用堆栈中的任意位置中,异常处理程序都没有找到合适(你写的)的 catch 块,就会自动终止该进程,并向用户显示(抛出)一条错误的信息。

  在这里我写了一个被 0 处会出现异常(会显式引发 DivideByZeroException 异常)的示例;如果在执行的途中出现异常,则捕获该异常。

         /// <summary>
/// 除法
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
static double Division(double x, double y)
{
if (y == )
{
throw new DivideByZeroException();
} return x / y;
} static void Main(string[] args)
{
//定义两个变量 x, y
double x = , y = ; try
{
var result = Division(x, y);
Console.WriteLine($"result: {result}");
}
catch (DivideByZeroException e)
{ Console.WriteLine(e);
} Console.Read();
}

二、异常的特点

  • 所有异常类型(包括自定义的异常)都是由基类 Exception 派生的。

  • 使用 try 块包围你认为可能会出现异常的代码。

  • 一旦 try 块中发生异常,控制流将按顺序找到与之关联的 catch 块,如果没有找到合适的,就会引发最终的异常基类 Exception 内的处理程序(前提你已经 catch)。

  • 如果出现异常却没有对应的异常处理程序,则该程序将会停止执行,并抛出对应错误的信息。

  • 在 catch 定义了的异常变量,可以获取对应异常类型的信息。比如调用堆栈的状态和错误的说明,具体看 Excetion 的属性。

  • throw 关键字可以显式引发异常。

  • 即使出现异常也会执行 finally 块中的代码。一般来说,我们会使用 finally 块释放资源,例如,关闭xx流。

三、怎样使用异常

  异常:指的是我们写的程序在运行时出现了错误,并且它会不断的蔓延、传播和扩散,有点像病毒一样。

  异常通常由错误的代码引发,可能是用户的错误输入,可能是一方没有按照约定来传输格式,也可能是数据传输的过程中被篡改。我们会对自己认为有可能报错的代码进行 catch ,这称为捕获异常。

  一旦引发了异常,这个异常将会在调用堆栈中一直向上进行传播,直到寻找到跟它匹配的 catch 语句。没有 catch 的异常会由系统提供的默认的异常处理程序进行处理,也就是你经常看到的一个突然造成调试中断并显示异常信息的对话框。

  所有的异常,它都是从 Exception 派生出来的,他们都包含了详细的异常描述属性。在这里我将自定义了一个新的异常类,然后使用 throw 关键字显式引发该对象(即异常)。

         /// <summary>
/// 定义新异常
/// </summary>
class MyException : Exception
{
public MyException(string msg) { }
} /// <summary>
/// 抛出新定义的异常
/// </summary>
static void ThrowMyExcetion()
{
throw new MyException("Sorry, this is test!");
}

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

         static void Main(string[] args)
{
try
{
ThrowMyExcetion(); //直接调用抛出异常的方法
}
catch (MyException e)
{
Console.WriteLine(e);
} Console.Read();
}

  如果引发异常的语句不在 try 块中,或者包含该语句的 try 块没有匹配的 catch 块,CLR 运行时将检查调用方法中是否有合适 try 语句和 catch 块。 运行时将在调用堆栈中继续往上搜索兼容(或匹配)的 catch 块。在找到并执行 catch 块之后,控制权将传递给 catch 块之后的下一个语句。

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

         static void Main(string[] args)
{
StreamWriter sw = null; try
{
sw = new StreamWriter(@"C:\book\小二和小三的故事.txt");
sw.Write("You are 250.");
}
catch (FileNotFoundException e)
{
//将具体的异常放在第一位
Console.WriteLine(e);
}
catch (IOException e)
{
//将并不具体的放在相对后面的位置
Console.WriteLine(e);
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
if (sw != null)
{
sw.Close();
}
} Console.Read();
}

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

         static void Main(string[] args)
{
FileStream fs = null;
FileInfo fi = new FileInfo(@"小二和小三的故事.txt"); try
{
fs = fi.OpenWrite();
fs.WriteByte();
}
finally
{
//记住哦,如果你忘记 close,将会引发 IO 异常!
//if (fs != null)
//{
// fs.Close();
//}
} try
{
fs = fi.OpenWrite();
fs.WriteByte();
Console.WriteLine("OK!");
}
catch (IOException e)
{
Console.WriteLine("Fail!");
} Console.Read();
}

  “Fail!”,这是因为上面注释了需要关闭文件流的语句,你可以尝试下去掉注释看看结果,记住哦,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:

         static void Main(string[] args)
{
try
{
//需要执行的代码
}
catch (Exception e)
{
//这里可以获取到被捕获的异常
//你需要知道自己应该如何处理该异常
}
}

  (2)try-finally:

             try
{
//需要执行的代码
}
finally
{
//在 try 块后执行的代码
}

  (3)try-catch-finally:

             try
{
//需要执行的代码
}
catch (Exception e)
{
//这里处理异常
}
finally
{
//在 try 块(也可能是 catch 块)后执行的代码
}

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

4.1 捕获异常的 Catch 块

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

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

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

  • 了解引发异常的原因,并可实现有选择性的恢复。例如,在捕获 FileNotFoundException 时你可以提示用户“文件找不到”和“请输入新的文件名”等。

  • 你也可以新建一个更具体或者说更具有代表性的异常,并选择引发该异常。

         double GetNum(double[] nums,int index)
{
try
{
return nums[index];
}
catch (IndexOutOfRangeException e)
{
throw new ArgumentOutOfRangeException("Sorry, 你想要的索引已经超出界限!");
}
}

  

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

             try
{
//尝试访问系统资源
}
catch (Exception e)
{
//伪代码:记录错误日志
log.Error(e); //再重新抛出错误
throw;
}

4.2 释放资源的 Finally 块

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

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

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

         static void Main(string[] args)
{
FileStream fs = null;
FileInfo fi = new System.IO.FileInfo("C:\\小二和小三的故事.txt"); try
{
fs = fi.OpenWrite();
fs.WriteByte();
}
finally
{
// 记得判断 null 哦,不然可能触发其它异常
if (fs != null)
{
fs.Close();
}
} }

C# 基础回顾系列

  《C# 知识回顾 - 委托 delegate》、《C# 知识回顾 - 委托 delegate (续)

  《C# 知识回顾 - 事件入门》、《C# 知识回顾 - Event 事件

  《string 与 String,大 S 与小 S 之间没有什么不可言说的秘密


【博主】反骨仔

【出处】http://www.cnblogs.com/liqingwen/p/6206251.html

【参考】微软官方文档

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

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

    你真的懂异常(Exception)吗? 目录 异常介绍 异常的特点 怎样使用异常 处理异常的 try-catch-finally 捕获异常的 Catch 块 释放资源的 Finally 块 一.异常介 ...

  2. C# 知识回顾 - 装箱与拆箱

    装箱与拆箱 目录 生活中的装箱与拆箱 C# 的装箱与拆箱 值类型和引用类型 装箱 拆箱 生活中的装箱与拆箱    我们习惯了在网上购物,这次你想买本编程书 -- <C 语言从入门到放弃> ...

  3. [C#] C# 知识回顾 - 装箱与拆箱

    装箱与拆箱 目录 生活中的装箱与拆箱 C# 的装箱与拆箱 值类型和引用类型 装箱 拆箱 读者见解 生活中的装箱与拆箱    我们习惯了在网上购物,这次你想买本编程书 -- <C 语言从入门到放弃 ...

  4. [C#] C# 知识回顾 - Lambda

    C# 知识回顾 - Lambda 序 它是第十一个希腊字母,一个拥有失意.无奈.孤独.低调等含义的流行符号,也指示一款称为“半条命”的游戏. 不过,这次我所讲的是 C# 中的 Lambda. 目录 L ...

  5. [C#] C# 知识回顾 - 学会处理异常

    学会处理异常 你可以使用 try 块来对你觉得可能会出现异常的代码进行分区. 其中,与之关联的 catch 块可用于处理任何异常情况. 一个包含代码的 finally 块,无论 try 块中是否在运行 ...

  6. [C#] C# 知识回顾 - 学会使用异常

    学会使用异常 在 C# 中,程序中在运行时出现的错误,会不断在程序中进行传播,这种机制称为“异常”. 异常通常由错误的代码引发,并由能够更正错误的代码进行 catch. 异常可由 .NET 的 CLR ...

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

    异常介绍 我们平时在写程序时,无意中(或技术不够),而导致程序运行时出现意外(或异常),对于这个问题, C# 有专门的异常处理程序. 异常处理所涉及到的关键字有 try.catch 和 finally ...

  8. 程序猿修仙之路--数据结构之你是否真的懂数组? c#socket TCP同步网络通信 用lambda表达式树替代反射 ASP.NET MVC如何做一个简单的非法登录拦截

    程序猿修仙之路--数据结构之你是否真的懂数组?   数据结构 但凡IT江湖侠士,算法与数据结构为必修之课.早有前辈已经明确指出:程序=算法+数据结构  .要想在之后的江湖历练中通关,数据结构必不可少. ...

  9. [C#] C# 知识回顾 - 序列化

    C# 知识回顾 -  序列化 [博主]反骨仔 [原文地址]http://www.cnblogs.com/liqingwen/p/5902005.html 目录 序列化的含义 通过序列化保存对象数据 众 ...

随机推荐

  1. “.Net 社区虚拟大会”(dotnetConf) 2016 Day 3 Keynote: Scott Hanselman

    美国时间 6月7日--9日,为期三天的微软.NET社区虚拟大会正式在 Channel9 上召开,美国时间6.9 是第三天, Scott Hanselman 做Keynote.今天主题围绕的是.NET ...

  2. ASP.NET MVC5+EF6+EasyUI 后台管理系统(81)-数据筛选(万能查询)

    系列目录 前言 听标题的名字似乎是一个非常牛X复杂的功能,但是实际上它确实是非常复杂的,我们本节将演示如何实现对数据,进行组合查询(数据筛选) 我们都知道Excel中是如何筛选数据的.就像下面一样 他 ...

  3. 使用 Android Studio 检测内存泄漏与解决内存泄漏问题

    本文在腾讯技术推文上 修改 发布. http://wetest.qq.com/lab/view/63.html?from=ads_test2_qqtips&sessionUserType=BF ...

  4. 【基于WPF+OneNote+Oracle的中文图片识别系统阶段总结】之篇一:WPF常用知识以及本项目设计总结

    篇一:WPF常用知识以及本项目设计总结:http://www.cnblogs.com/baiboy/p/wpf.html 篇二:基于OneNote难点突破和批量识别:http://www.cnblog ...

  5. Python爬虫小白入门(四)PhatomJS+Selenium第一篇

    一.前言 在上一篇博文中,我们的爬虫面临着一个问题,在爬取Unsplash网站的时候,由于网站是下拉刷新,并没有分页.所以不能够通过页码获取页面的url来分别发送网络请求.我也尝试了其他方式,比如下拉 ...

  6. [WPF] Wait for a moment.

    一.控件介绍 在 WPF 中使用的等待控件,控件包括三种,普通的等待信息提示(WaitTip),进度条提示(WaitProgress),以及主程序覆盖的模拟时钟等待窗口(WaitClock),具体效果 ...

  7. 信息安全-1:python之playfair密码算法详解[原创]

    转发注明出处: http://www.cnblogs.com/0zcl/p/6105825.html 一.基本概念 古典密码是基于字符替换的密码.加密技术有:Caesar(恺撒)密码.Vigenere ...

  8. 定时Job在IIS中潜在危险-IIS 定期回收

    引言 有时我们会在IIS中启用一些定时服务,但是你必须清楚IIS会定期回收Asp.net的应用程序的.首先来看IIS啥时候回收APPDomain.   APPDomain 回收时机 There are ...

  9. css 填坑常用代码分享

    以下是常用的代码收集,没有任何技术含量,只是填坑的积累.转载请注明出处,谢谢. 因为提交比较麻烦,后来转置github:https://github.com/jsfront/src/blob/mast ...

  10. Debian下安装mono

    从mono的官网上查,debian的步骤写得太乱了.其实总结起来,就是这么几步: apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --rec ...