在尝试捕获涉及基类和派生类的异常类型时要注意catch子句的排序方式,因为基类的catch子句也会匹配它的任何派生类。例如,由于所有异常的基类都是Exception,因此带有catch的Exception会捕获所有可能的异常。当然,根据前面的描述,使用不带异常类型的catch提供了一种捕获所有异常的清晰方式。然而在其他背景下,捕获派生类异常的问题非常重要,尤其是在创建自己的异常时。
如果要捕获基类类型的异常和派生类类型的异常,那么将派生类先放在catch序列中。之所以有必要这样做,是因为基类catch也会捕获所有派生类。幸运的是,这个规则是自实施的,因为先放置基类会导致编译时错误。
下面的程序创建了分别名为ExceptA和ExceptB的两个异常类。ExceptA派生自Exception,ExceptB派生自ExceptA。然后程序抛出各个类型的异常。为了简单起见,仅提供了一个构造函数(接受一个描述异常的字符串作为实参)。但是要记住,在商业代码中,自定义的异常类一般会提供Exception定义的所有4个构造函数。
- // Derived exceptions must appear before base class exceptions.
- using System;
- // Create an exception.
- class ExceptA : Exception {
- public ExceptA(string str) : base(str) { }
- public override string ToString() {
- return Message;
- }
- }
- // Create an exception derived from ExceptA
- class ExceptB : ExceptA {
- public ExceptB(string str) : base(str) { }
- public override string ToString() {
- return Message;
- }
- }
- class OrderMatters {
- static void Main() {
- for(int x = 0; x < 3; x++) {
- try {
- if(x==0) throw new ExceptA("Caught an ExceptA exception");
- else if(x==1) throw new ExceptB("Caught an ExceptB exception");
- else throw new Exception();
- }
- catch (ExceptB exc) {
- Console.WriteLine(exc);
- }
- catch (ExceptA exc) {
- Console.WriteLine(exc);
- }
- catch (Exception exc) {
- Console.WriteLine(exc);
- }
- }
- }
- }
该程序的输出如下所示:
- Caught an ExceptA exception
- Caught an ExceptB exception
- System.Exception: Exception of type 'System.Exception' was thrown.
- at OrderMatters.Main()
注意catch子句的顺序,这是这些子句能够运行的唯一顺序。由于ExceptB是从ExceptA派生的,因此ExceptB的catch必须在ExceptA的catch之前。类似地,Exception(它是所有异常的基类)的catch必须最后出现。要亲自证实这一点,可以重新排列catch子句。这样做会导致编译时错误。
问:既然异常通常表示特定的错误,那么为什么要捕获基类异常呢?
答:捕获基类异常的catch子句可以捕获整个类别的所有异常,可以使用一个catch处理它们,从而可以避免重复的代码。例如,可以创建一组描述某种设备错误的异常。如果异常处理程序只是告诉用户发生了一种设备错误,那么可以对这种类型的所有异常使用一个共同的catch。处理程序可以简单地显示Message字符串。由于完成该操作的代码对于所有异常都是相同的,因此只使用一个catch就可以响应所有设备异常。
在本项目中,将创建可以由第9章的"试一试"一节中开发的队列类使用的两个异常类。这两个异常类将指出队列满与队列空时错误发生的条件。当发生错误时,分别由Put()和Get()方法抛出异常。为了简洁起见,本示例仅向SimpleQueue类中添加这些异常,但是可以方便地将它们合并到其他队列类中。为了简短起见,异常类仅实现程序中实际使用的构造函数(它是接受一个描述异常的实参的构造函数)。您可以尝试亲自添加其他构造函数。
操作步骤
(1) 创建名为QExcDemo.cs的文件。
(2) 在QExcDemo.cs中,定义如下异常:
- // Add exception handling to the queue classes.
- using System;
- // An exception for queue-full errors.
- class QueueFullException : Exception {
- public QueueFullException(string str) : base(str) { }
- // Add other QueueFullException constructors here, if desired.
- public override string ToString() {
- return "\n" + Message;
- }
- }
- // An exception for queue-empty errors.
- class QueueEmptyException : Exception {
- public QueueEmptyException(string str) : base(str) { }
- // Add other QueueEmptyException constructors here, if desired.
- public override string ToString() {
- return "\n" + Message;
- }
- }
当试图将元素存储到已满的队列中时,生成QueueFullException异常。当试图从空队列中删除元素时,生成QueueEmptyException异常。
(3) 按如下代码所示修改SimpleQueue类,使它在发生错误时抛出异常。将修改后的类添加到QExcDemo.cs中。
- // A simple, fixed-size queue class for
characters that uses exceptions.- class SimpleQueue : ICharQ {
- char[] q; // this array holds the queue
- int putloc, getloc; // the put and get indices
- // Construct an empty queue given its size.
- public SimpleQueue(int size) {
- q = new char[size+1]; // allocate memory for queue
- putloc = getloc = 0;
- }
- // Put a character into the queue.
- public void Put(char ch) {
- if(putloc==q.Length-1)
- throw new QueueFullException("Queue Full! Max length is " +
- (q.Length-1) + ".");
- putloc++;
- q[putloc] = ch;
- }
- // Get a character from the queue.
- public char Get() {
- if(getloc == putloc)
- throw new QueueEmptyException("Queue is empty.");
- getloc++;
- return q[getloc];
- }
- }
向SimpleQueue类中添加异常可以以合理的方式处理队列错误。SimpleQueue类以前的版本只是简单地报告错误。抛出异常的方式要好很多,因为它允许使用SimpleQueue类的代码以适当的方式处理错误。
(4) 为了尝试使用更新后的SimpleQueue类,将如下所示的QExcDemo类添加到QExcDemo.cs中。
- // Demonstrate the queue exceptions.
- class QExcDemo {
- static void Main() {
- SimpleQueue q = new SimpleQueue(10);
- char ch;
- int i;
- try {
- // Overrun the queue.
- for(i=0; i < 11; i++) {
- Console.Write("Attempting to store : " +
- (char) ('A' + i));
- q.Put((char) ('A' + i));
- Console.WriteLine(" -- OK");
- }
- Console.WriteLine();
- }
- catch (QueueFullException exc) {
- Console.WriteLine(exc);
- }
- Console.WriteLine();
- try {
- // Over-empty the queue.
- for(i=0; i < 11; i++) {
- Console.Write("Getting next char: ");
- ch = q.Get();
- Console.WriteLine(ch);
- }
- }
- catch (QueueEmptyException exc) {
- Console.WriteLine(exc);
- }
- }
- }
(5) 为了创建程序,必须使用IQCar.cs文件编译QExcDemo.cs。IQChar.cs中包含了队列接口。当运行QExcDemo时,将看到如下输出:
- Attempting to store : A -- OK
- Attempting to store : B -- OK
- Attempting to store : C -- OK
- Attempting to store : D -- OK
- Attempting to store : E -- OK
- Attempting to store : F -- OK
- Attempting to store : G -- OK
- Attempting to store : H -- OK
- Attempting to store : I -- OK
- Attempting to store : J -- OK
- Attempting to store : K
- Queue Full! Max length is 10.
- Getting next char: A
- Getting next char: B
- Getting next char: C
- Getting next char: D
- Getting next char: E
- Getting next char: F
- Getting next char: G
- Getting next char: H
- Getting next char: I
- Getting next char: J
- Getting next char:
- Queue is empty.