当前位置: 首页 > news >正文

.NET中的Exception处理(C#)

摘要:
本文以C#为编程语言,讨论了 .NET 中的异常处理方式,主要包括 try/catch 块、finally语句、Exception 对象、throw语句等主题。
--------------------------------------------------------------------------------

本文内容
理解异常的基本概念
使用 try/catch 块处理异常
理解finally的意义
使用 Exception 对象确定异常
将异常返回过程调用程序

--------------------------------------------------------------------------------

基本概念
就像其他面向对象语言一样,C#采用异常(exception)来应对程序错误和非正常情况。

异常是包含程序非正常事件信息的对象。与缺陷(bug)不同,一个bug是程序员的疏漏,它们应该在产品发布前被更正;尽管一个bug可能引发异常的抛出,你不应该完全依靠异常来处理你的bug,它至多是你测试的手段,你应该自己更正哪些bug。类似的,错误(error)是由用户操作而引起,比如在一个应该输入字母的地方用户输入了一个数字;虽然它也可能引发异常,但你应该通过校验代码(validation code)来抓住这些错误。无论何时,在可能的情况下错误都应该是能预料和能被预防的。即使你除去了所有的bug和列举了所有可能的用户错误,你仍会遇到无法预料和阻止的异常,如内存耗尽、网络崩溃。你无法预防异常,但你能处理它们,以避免它们使你的程序崩溃。

当你的程序遇到一个非正常情况,比如说内存不足,它就会引发(throw/raise)一个异常。此时,当前的过程调用将挂起,.NET 运行时(CLR)将从下至上搜索过程调用堆栈,以查找相应的异常处理程序。也就是说,如果抛出异常的代码正处于某个 Try 块中,运行时将首先使用本地的 Catch 块(如果有)来处理异常(它将执行在该位置找到的 Catch 块代码),否则这个程序段将被终止并将异常的处理权交给其调用函数;如果没有函数处理此异常(即在整个调用堆栈中没能找到适当的 Catch 块),最终运行时将会得到并处理它,并立刻将你的程序终止。


--------------------------------------------------------------------------------

引发错误
示例为一个简单的文件打开操作并检索其长度的程序(以后几个示例的内容基本相同),示例从窗体文本框textBoxfilepath中得到文件名:

string filepath=this.textBoxfilepath.Text;
long isize;
FileStream fs=File.Open(filepath,FileMode.Open);
isize=fs.Length;
fs.Close();

有许多原因会使代码引发异常,如文件不存在,访问权限不够等等。在现在这种没有异常处理的情况下,运行时发生的任何错误会回溯到.NET运行时(CLR);而运行时会呈现给用户一个让人费解并可能造成危险的对话框(图1)。为了避免出现此对话框,如果发生运行时错误,您至少需要向顶层过程添加异常处理,并在必要时在下层过程中也添加异常处理。


图1:包含的Continue按钮使 .NET 默认错误处理程序变得有些危险。

此外,其中的详细信息并不是您希望用户看到的内容。


--------------------------------------------------------------------------------

添加简单try/catch块处理异常
在C#中,为了恰当地处理运行时异常,在要需要保护的任何代码附近添加一个 try/catch块。在 try 块的代码中发生的任何运行时异常都将立即使用 catch 块中的代码继续执行:

try
{
string filepath=this.textBoxfilepath.Text;
long isize;
FileStream fs=File.Open(filepath,FileMode.Open);
isize=fs.Length;
fs.Close();
}
catch
{
MessageBox.Show("error occured!");
}

这段代码运行时,当有异常出现(如文件不存在)程序不会显示图2的对话框,取而代之的是一条简单的"error occurred!"警告,因为try块捕获住了异常并立即转到catch块中的代码继续执行。

注意:如果catch块中没有退出的代码(如return,throw),catch块后的代码将继续得到执行。并且try 块后面至少需要包含一个 catch 块(有关包含多个 catch 块的详细信息,请参阅下文)或一个finally块(参阅下文)。


--------------------------------------------------------------------------------

处理特定异常(多个catch块)
.NET 框架提供了大量的特定异常类,所有这些异常类都是从基类 Exception 类继承而来的。在 .NET 框架文档中,您会看到一些表,它们列出了在调用任何方法时都可能出现的所有异常。图 2便列出了.NET中File.Open()方法调用时可能发生的所有异常情况:


图 2: File.Open 可能发生的所有异常

你可以在一个try块后添加足够多的 Catch 块,以便对不同的异常情况作出不同的处理。以下的代码就列举了对几个不同的异常进行不同处理的情况。

try
{
string filepath=this.textBoxfilepath.Text;
long isize;
FileStream fs=File.Open(filepath,FileMode.Open);
isize=fs.Length;
fs.Close();
}
catch(UnauthorizedAccessException uex)
{
MessageBox.Show(uex.Message);
}
catch(FileNotFoundException fex)
{
MessageBox.Show(fex.Message);
}
catch(NotSupportedException nex)
{
MessageBox.Show(nex.Message);
}
catch(ArgumentException aex)
{
MessageBox.Show(aex.Message);
}
catch
{
MessageBox.Show("error occured!");
}

注意:catch块的次序必须十分小心,比如一个DivideByZeroException 异常继承自ArithmeticException异常,如果你先捕获后者,则当除数为0时抛出的异常就会进入ArithmeticException块而永远不会进入DivideByZeroException块。事实上,当这种情况出现时,编译器会发现DivideByZeroException块不能被执行到,并会报告一个编译错误。


--------------------------------------------------------------------------------

Finally关键字
除了 try 和 catch 块中的代码外,有时你还需要添加无论何种情况下都会被执行到的代码,比如你可能需要释放一些资源、关闭一个文件等等,这是就需要使用Finally 关键字在 Catch 块后添加 Finally 块。即使代码抛出异常,并在Catch 块中添加了显式的return语句,finally块中的代码仍会被执行。Finally 块中的代码将在异常处理代码之后、控制返回到调用过程之前执行。

注意:finally 块只需要一个 try 块,catch 块的存在与否对其并没有影响。使用break,continue,return语句退出 finally 块都是非法的。

我们对上面的示例进行如下修改,以使任何情况下都可以调用结束代码,关闭可能打开了的文件:

FileStream fs=null;
try
{
string filepath=this.textBoxfilepath.Text;
long isize;
fs=File.Open(filepath,FileMode.Open);
isize=fs.Length;
}
catch(FileNotFoundException fex)
{
MessageBox.Show(fex.Message);
}
catch
{
MessageBox.Show("error occured!");
}
finally
{
//无论发生什么情况,下面的代码都将被执行到
if(null!=fs)
{
fs.Close();
}
}


--------------------------------------------------------------------------------

Throw 关键字
在C#中,要通知一个非正常情况,你可以使用 throw 关键字抛出一个异常。下面一行代码创建一个新的System.Exception实例,并将它抛出:

throw new System.Exception();

抛出的异常和所有自然引发的异常一样,立即将代码段挂起,并由CLR寻找一个异常处理者。如果要截取不同的异常并将它们作为单个异常类型全部返回到调用程序,使用 Throw 语句可以非常轻松地完成此操作。比如有一段代码捕获所有异常,而且无论导致异常的原因是什么,都只抛出一个 FileNotFoundException 对象给调用程序,因为实际的过程调用者可能并不关心实际发生的事情,也不关心为什么无法找到文件,他只关心该文件是否可用,并且需要从其他不同的异常中辨别该特定异常。


--------------------------------------------------------------------------------

Exception对象
到目前为止我们一直使用Exception作为错误的信号,但未真正接触Exception对象本身,接着我们就来讨论这个问题。


图3:Exception 类的公共成员

图3是Exception类的公共成员变量。其中 Message 只读属性提供了关于这个异常的信息,比如为什么它被抛出,抛出异常的代码能在Exception构造函数中设定 Message 属性值。HelpLink 属性提供了一个此异常帮助文件的一个链接。而 StackTrace 只读属性是在运行时被设置的。在下面的例子中,Exception.HelpLink 属性被设置以提供用户关于DivideByZeroException的帮助,而异常的 StackTrace 属性提供一个对错误语句的运行栈追踪,显示了堆栈信息和导致异常抛出的一系列方法调用。

public class Test
{
public static void Main( )
{
Test t = new Test( );
t.TestFunc( );
}
// try to divide two numbers
// handle possible exceptions
public void TestFunc( )
{
try
{
Console.WriteLine("Open file here");
double a = 12;
double b = 0;
Console.WriteLine ("{0} / {1} = {2}",a, b, DoDivide(a,b));
Console.WriteLine ("This line may or may not print");
}
// most derived exception type first
catch (System.DivideByZeroException e)
{
Console.WriteLine("
DivideByZeroException! Msg: {0}",e.Message);
Console.WriteLine("
HelpLink: {0}", e.HelpLink);
Console.WriteLine("
Here's a stack trace: {0}
",e.StackTrace);
}
catch
{
Console.WriteLine("Unknown exception caught");
}
finally
{
Console.WriteLine ("Close file here.");
}
}
// do the division if legal
public double DoDivide(double a, double b)
{
if (b == 0)
{
DivideByZeroException e =new DivideByZeroException( );
e.HelpLink =" http://www.libertyassociates.com";
throw e;
}
if (a == 0)
{
throw new ArithmeticException( );
return a/b;
}
}
}

Output:

Open file here
DivideByZeroException! Msg: Attempted to divide by zero.
HelpLink: http://www.libertyassociates.com
Here's a stack trace:
at Programming_CSharp.Test.DoDivide(Double a, Double b)
in c:...exception06.cs:line 56
at Programming_CSharp.Test.TestFunc( )
in...exception06.cs:line 22
Close file here.

在输出中,stack trace反向列出了被调用的方法,显示错误在DoDivide( )中发生,而此函数被TestFunc()调用。当多种方法纠缠在一起,stack trace能帮助你理清方法调用的次序。

在异常被抛出之前,你可以设置 HelpLink 属性:

e.HelpLink = " http://www.libertyassociates.com";
这让你能为用户提供有用的信息。


--------------------------------------------------------------------------------

小结
使用一个 Try/Catch 块可以向代码段添加异常处理。
.NET 运行时可以依次处理 Catch 块,它将使用找到的第一个匹配块。
您可以嵌套 Try 块,以便轻松而有效地推入和弹出异常处理状态。
在 Try 块后添加一个 Finally 块,这样无论发生什么,都可以无条件运行代码。

相关文章:

  • C#中调用exe文件示例
  • J2ME程序开发全方位基础讲解汇总
  • CANoe工程通过控件改变报文信号值的两种方法
  • C#中对串口的操作
  • 看不见的含量
  • 诊断会话控制及保持/ECU复位(ISO14229系列之10/3E/11服务)
  • 就计算机学习问题与一名高二学生的邮件往来
  • ISO 15765-2(网络层服务)
  • 关于自动化诊断测试(CANoe.Diva的应用)
  • ISO14229之概述
  • 需求增加也要走保证金流程——CSDN外包实践(62)
  • DBC文件解析
  • Sun中国工程研究院院长王星耀:开源策略是为了挤掉微软
  • 使用DBCView编辑DBC文件过程(一)
  • 使用DBCView编辑DBC文件过程(二)
  • 【162天】黑马程序员27天视频学习笔记【Day02-上】
  • download使用浅析
  • Git 使用集
  • GitUp, 你不可错过的秀外慧中的git工具
  • Laravel核心解读--Facades
  • Linux Process Manage
  • mysql 数据库四种事务隔离级别
  • ng6--错误信息小结(持续更新)
  • React的组件模式
  • Redis字符串类型内部编码剖析
  • Selenium实战教程系列(二)---元素定位
  • ------- 计算机网络基础
  • 简单基于spring的redis配置(单机和集群模式)
  • 精彩代码 vue.js
  • 如何进阶一名有竞争力的程序员?
  • 【云吞铺子】性能抖动剖析(二)
  • 阿里云ACE认证之理解CDN技术
  • # Swust 12th acm 邀请赛# [ A ] A+B problem [题解]
  • #我与Java虚拟机的故事#连载19:等我技术变强了,我会去看你的 ​
  • $.type 怎么精确判断对象类型的 --(源码学习2)
  • (1)(1.11) SiK Radio v2(一)
  • (173)FPGA约束:单周期时序分析或默认时序分析
  • (4)STL算法之比较
  • (7)STL算法之交换赋值
  • (pojstep1.1.2)2654(直叙式模拟)
  • (九十四)函数和二维数组
  • (十八)用JAVA编写MP3解码器——迷你播放器
  • (四)【Jmeter】 JMeter的界面布局与组件概述
  • (学习日记)2024.01.09
  • (原創) 未来三学期想要修的课 (日記)
  • (转)【Hibernate总结系列】使用举例
  • (转载)PyTorch代码规范最佳实践和样式指南
  • .NET CF命令行调试器MDbg入门(二) 设备模拟器
  • .NET/C# 使用反射调用含 ref 或 out 参数的方法
  • .NET/MSBuild 中的发布路径在哪里呢?如何在扩展编译的时候修改发布路径中的文件呢?
  • .sdf和.msp文件读取
  • @require_PUTNameError: name ‘require_PUT‘ is not defined 解决方法
  • [ element-ui:table ] 设置table中某些行数据禁止被选中,通过selectable 定义方法解决
  • [].shift.call( arguments ) 和 [].slice.call( arguments )
  • [383] 赎金信 js