一、简介
如果程序做得不是很完善,比如一个接收数字的文本框,输入了字母或中文,将会弹出错误并中断程序。
本章将讨论如何处理这些问题,从而使得应用程序变得更加完善。
C#异常处理功能提供了处理程序运行时出现的任何意外或异常情况的发放。
使用try、catch和finally关键字来尝试可能未成功的操作】处理失败以及在事后清理资源。
二、异常的出现
许多原因造成异常的出现,如算术溢出、堆栈溢出、内存不足、参数越界、数组索引越界、试图访问已经释放的资源等。
1.异常演示
1)新建一个windows应用程序
2)修改窗体属性,Name:frmException;Text:测试异常;
3)放置1个Button控件。Name:btnGetNum;Text:获取数字;
4)放置1个TextBox控件。Name:txtNum;
5)放置1个ListBox控件。Name:IsNum;
6)双击btnGetNum按钮控件,生成Click事件。
private void btnGetNum_Click(object sender, EventArgs e){ int num = int.Parse(txtNum.Text); IstNum.Items.Add(num);}
运行结果
点击“获取数字”,就会弹出异常。
这是因为int.Parse(txtNum.Text)希望把文本框内的字符串转换为一个32位整数类型。
而输入的是字母或中文并不是由数字组成的字符串,所以无法进行转换。
从而抛出异常。
三、try-catch
在C#语言中可以使用try-catch语句去捕捉和处理有可能发生的异常。
try-catch语句由一个try块和其后所跟的一个或多个catch子句构成。
表达形式:
try{ try 语句块}catch(异常声明1){ catch 语句块1}catch(异常声明2){ catch 语句块2}
try语句块:包含有可能会引发异常的语句块。
异常声明:有可能会引发的异常类型,如FormatException。
catch语句块:指定的异常引发后,对异常进行相应处理。
1.异常的捕捉
示例:
沿用上面的案例,并修改Button控件的单击事件。
private void btnGetNum_Click(object sender, EventArgs e) { int num; try { num = int.Parse(txtNum.Text); } catch (System.FormatException) { IstNum.Items.Add("你所输入的数字不合法!"); return; //跳出btnGetNum_Click的单击事件 } IstNum.Items.Add(num);//打印文本框内的数字 }
运行结果
单击“获取数字”,如果不是数字,会显示“你所输入的数字不合法!”
如果是数字,就打印;
分析:
先声明一个int类型。
这是因为如果在try内声明,就成了局部变量了!也就无法在后面访问。
try语句就类似一种判断语句,判断是否会出现异常。
抛出异常后,就执行catch语句。
而System.FormatException,是一个异常类。
当参数格式不符合调用方法的参数规范时将引发这个异常。
可以声明FormatException类的一个对象并使用它来获取一些有关这个异常的信息,比如获取异常自带的描述信息。
将catch代码更改为:
catch (System.FormatException ex){ IstNum.Items.Add(ex.Message); //直接打印异常信息 return; }
运行结果
由此可见,返回的是系统默认的信息,就是“输入字符串的格式不正确。”
并不是自己定义的,而是系统默认。
而return语句是必须的,否则不会中断整个单击事件,而是继续运行,从而再报错。
2.一般情况下,在catch语句中可以使用以下几种方法来处理异常以获得不同的执行路径。
1)不写任何调整代码:
使得系统忽略异常,程序会继续往下执行。
2)使用return语句:
使得程序直接跳出方法体回到调用方法的地方。
3)使用throw语句:
使得异常再次被抛出,表示当前异常处理代码无法处理此类异常,将异常转给更上一级的异常处理程序进行处理。
catch (System.FormatException ex){ throw ex; //将异常再次抛出}
4)使用System.Environment.Exit(1)语句:
它将直接关闭应用程序,一般情况下不使用这样的方法。
而上述程序还存在一个问题,就是取值范围,当超出int类型的范围,就会弹出异常。
如:输入99999999999999999999999999
3.捕捉多个异常。
在C#语句中,try语句块后面可以跟多个catch块,也就是说可以同时捕捉多个异常。
1)沿用上面开头的案例,修改Button按钮控件的单击事件。
private void btnGetNum_Click(object sender, EventArgs e){ int num; try { num = int.Parse(txtNum.Text); } catch (System.FormatException) { IstNum.Items.Add("你所输入的数字不合法!"); return; } catch (System.OverflowException) { IstNum.Items.Add("你所输入的整数超出了范围!"); return; } IstNum.Items.Add(num);//打印文本框内的数字 }
运行结果
4.捕捉Exception异常。
如果无法预知应用程序会引发什么样的异常,可以直接使用Exception来捕获C#语言中所有的异常。
1)沿用上面开头的案例,修改Button按钮控件的单击事件。
private void btnGetNum_Click(object sender, EventArgs e){ int num; try { num = int.Parse(txtNum.Text); } catch (System.Exception ex) //注意这里 { IstNum.Items.Add(ex.Message); return; } IstNum.Items.Add(num);//打印文本框内的数字 }
运行结果
这异常会根据异常的不同,而返回不同的提示。
如果在一个try语句后面要跟随多个catch语句,就需要把更具体的异常放置前面。
也就是说,Exception异常是最不具体的,所有不能放最前面。
5.常见的异常列表
异常类名称 | 简单描述 |
MemberAccessException | 访问错误:类型成员不能被访问 |
ArgumentException | 参数错误:方法的参数无效 |
ArithmeticException | 数学计算错误:由于数字运算导致的异常 |
ArrayTypeMismatchException | 数组类型不匹配 |
DivideByZeroException | 被0除 |
FormatException | 参数的格式不正确 |
IndexOutOfRangeException | 索引超出范围 |
InvalidCastException | 非法强制转换,在显式转换失败时引发 |
MulticastNotSupportedException | 不支持的组播:组合2个非空委派失败时引发 |
NotSupportedException | 调用的方法在类中没有实现 |
NullReferenceException | 引用空引用对象时引发 |
OutOfMemoryException | 无法为新语句分配内存时引发,内存不足 |
OverflowException | 溢出 |
StackOverflowException | 栈溢出 |
TypeInitializationException | 错误的初始化类型:静态构造函数有问题时引发 |
NotFiniteNumberException | 无限大的值:数字不合法 |
四、校验(checked)和非校验(unchecked)语句
默认情况下,整型算术运算中,如果表达式产生的值超出了目标类型的范围,则常数表达式将导致编译时的错误,而非常数表达式在运行时计算并不会引发异常。
1.整型算术运算溢出
1)新建一个windows应用程序。
2)修改窗体属性,Name:frmIntFlow;Text:整型算术运算溢出。
3)放置1个Button控件,Name:btnCalc;Text:计算。
4)放置1个TextBox控件,Name:txtResult;
5)双击Button按钮控件,生成Click单击事件。
private void btnCalc_Click(object sender, EventArgs e) { int result, a, b; a = b = 1000000; try { result = a * b; } catch(System.OverflowException) { MessageBox.Show("算术运算发生溢出!"); return; } txtResult.Text = result.ToString(); }
运行结果
可以看到1000000*1000000,结果却等于-727379968。
得出这样的结果,是因为赋给了32位整型变量时发生溢出,但并没有引发任何异常。
但却并不是想要看到的结果,而解决这个问题有两个途径:
第一个方法,改变编译器的设置。
1)选择菜单项中的【项目】|【IntFlow属性】打开项目的属性窗口。
2)点击【生成】属性页,单击【高级】按钮打开【高级生成设置】窗口。
3)在【高级生成设置】对话框内勾选【检查运行上溢/下溢】复选框,并点击确定。
再次运行程序,会弹出“算术运算发生溢出!”对话框。
所以需要使用catch语句块来触发,OverflowException异常。
第二个方法:
使用校验(checked)语句。
Checked操作符和unchecked操作符用于在整型算术运算时,控制当前环境中的溢出检查。
下列运算参与了checked检查和unchecked检查(操作数均为整数)。
1)预定义的“++”和“--”一元运算符。
2)预定义的“-”一元运算符。
3)预定义的“+”、“-”、“*”、“/”等二元操作符。
4)从一种整型到另一种整型的显示数据转换。
当上述整型运算产生一个目标类型无法表示的大数时,可以使用checked语句包含整个运算过程,表明将对这些运算进行溢出检查。
若运算是常量表达式,则产生编译错误:The operation overflows at complie time in checked mode。
若运算是非常量表达式,则运行时会抛出一个溢出异常:OverFlowException异常。
2.用checked检查整型算术运算溢出
1)沿用这个项目,并查看是否去掉【检查运行上溢\下溢】的勾选。
2)运行查看,是否触发异常,如果没有就修改Button按钮控件的单击事件。
private void btnCalc_Click(object sender, EventArgs e) { int result, a, b; a = b = 1000000; try { result = checked( a * b); //在这里添加checked块 } catch(System.OverflowException) { MessageBox.Show("算术运算发生溢出!"); return; } txtResult.Text = result.ToString(); }
运行结果
写程序时,应当将可能发生非预期溢出的代码放到一个checked块中以表明这些代码需要进行溢出检查。
如果有多个连续的语句需要进行checked检查,则可以使用checked语句,并用大括号将这些语句包起来。
例:
checked{ result = a * b;}
3.unchecked语句
它的作用与checked语句正好相反。
它明确地标明了它所作用的语句块或表达式不需要进行溢出检查。
应当将允许发生溢出的代码放到一个unchecked块中,以表明这些代码允许发生溢出。
它的使用方法与checked相同。
注意:
实数类型,如float、double类型在运算时不会触发异常。
即使使用checked语句进行检查页不会触发异常。
decimal类型则可以引发异常,在进行钱币运算时,请使用decimal数据类型。
五、try-finally
C#语言是一种非常先进的语言,它拥有自动垃圾回收这样的高级机制。
这种机制使得程序开发人员从繁杂的内存管理工作中解脱出来,使编程工作变得轻松许多,程序员可以更专心于程序逻辑。
但是许多程序都需要使用如文件、数据库连接、套接字、互斥体等资源。
而这些非托管资源或本地资源通常在其对象的内存即将被回收时,必须执行一些资源清理代码。
如果在使用这些资源时发生了异常而导致程序中断,那么资源清理代码将得不到执行。
内存中将出现无法回收的垃圾,应当尽量避免这种情况的发生。
C#语言中,try-finally语句提供了解决这类问题的方法。
finally块一般用于清除在try块中分配的任何资源。
无论try块中的语句是否发生异常,总是执行finally块中的语句。
一般形式如下:
try{ try语句块 }finally{ finally语句块}
其中try语句块包含有可能引发异常的代码段,finally语句包含异常处理程序和清理代码。
在try-finally中可以加入catch用于处理语句块中出现的异常。
而finally用于保证代码语句块的执行。
一般形式变为:
try{ try语句块 }catch(异常声明1){ catch语句块1}...finally{ finally语句块}
1.用finally语句块强制执行某项代码。
1)还是使用这个案例界面。
2)把txtResult输入框控件的Multiline属性设置为true,也就是自己调节输入框的大小。
3)修改计算Button按钮控件的Click代码。
private void btnCalc_Click(object sender, EventArgs e) { int result, a, b; a = b = 100; //为了不触发异常 try { result = checked( a * b); //在这里添加checked块 txtResult.Text += result.ToString() + "\r\n"; //这句提取了 } catch(System.OverflowException) { txtResult.Text += "算术运算发生溢出!\r\n"; return; } finally { txtResult.Text += "执行一些清理资源工作!\r\n"; } }
运行结果:
如果把第4行改为:
a = b = 1000000;
再进行执行后,会爆出异常。
由此可以看出,无论是不是异常,都会执行finally语句块。
一般情况下,finally块内放置的是清理资源的代码。
而这里用打印输出进行代替。
注意:
try-finally是一种比较耗系统资源的操作,在编写对速度要求高的算法代码时,应尽可能减少使用。