【C#学习】异常处理

本文详细介绍了C#中的异常处理机制,包括try-catch-finally语句的使用、异常捕获和传播规则,以及何时创建自定义异常。重点讨论了catch块的灵活性,finally块的执行条件,以及异常对象的属性如Message、Source和StackTrace等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

官方文档:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/exceptions/

C# 异常处理和C++很像,也是 try, catch , final。C# 语言的异常处理功能有助于处理在程序运行期间发生的任何意外或异常情况。 异常处理功能使用 try、catch 和 finally 关键字来尝试执行可能失败的操作、在你确定合理的情况下处理故障,以及在事后清除资源。 公共语言运行时 (CLR)、.NET Framework/任何第三方库或应用程序代码都可以生成异常。 异常是使用 throw 关键字创建而成。C# 中的异常是最终全都派生自 System.Exception 的类型。

异常具有以下属性: 

  1. 异常是最终全都派生自 System.Exception 的类型。
  2. 在可能抛出异常的语句周围使用 try 代码块。
  3. 在 try 代码块中出现异常后,控制流会跳转到调用堆栈中任意位置上的首个相关异常处理程序。 在 C# 中,catch 关键字用于定义异常处理程序。
  4. 如果给定的异常没有对应的异常处理程序,那么程序会停止执行,并显示错误消息。
  5. 除非可以处理异常并让应用程序一直处于已知状态,否则不捕获异常。 如果捕获 System.Exception,使用 catch 代码块末尾的 throw 关键字重新抛出异常。
  6. 如果 catch 代码块定义异常变量,可以用它来详细了解所发生的异常类型。
  7. 使用 throw 关键字,程序可以显式生成异常。
  8. 异常对象包含错误详细信息,如调用堆栈的状态和错误的文本说明。
  9. 即使有异常抛出,finally 代码块中的代码仍会执行。 使用 finally 代码块可释放资源。例如,关闭在 try 代码块中打开的任何流或文件。

使用异常

异常可由 .NET Framework 公共语言运行时 (CLR) 或由程序中的代码引发。 finally 块无论是否抛出异常都会被执行,且在try和catch之后执行,即便try和catch中可能会return, continue, break, goto等等,finally块都是永远被执行

  1. catch 关键字后指定的异常对象是catch块的局部变量,在该catch块以外无效。如果catch块中不会用到异常对象,则括号里可以只写异常对象的类型,不必写异常对象的名称;如果只写了catch关键字,没有写括号和异常对象,那么是默认的捕获类型Exception。且这种catch块必须在常规catch块的最后,且只能有一个。
  2. finally 块必须在try 和 catch块的后面。且 finally 块中不允许 return。如果在finally 块中用了break, continue, 和 goto 语句,则目的地也必须还在此finally 块中。总结来说,finally 块中的跳转语句不能跳出代码段之外。
  3. try 和 catch块的跳转(return, continue, break, goto)没有限制。
  4. 在try和catch块中都可以 throw 抛出异常。

异常处理有三种形式的写法,必须至少有一个catch或者finally块,一个不具有 catch 或 finally 块的 try 块会导致编译器错误。:

try-catch 语句

try
{
    // Code to try goes here.
}
catch (SomeSpecificException ex)
{
    // Code to handle the exception goes here.
    // Only catch exceptions that you know how to handle.
    // Never catch base class System.Exception without
    // rethrowing it at the end of the catch block.
}

try-finally 语句

try
{
    // Code to try goes here.
}
finally
{
    // Code to execute after the try block goes here.
}

try-catch-finally 语句

try
{
    // Code to try goes here.
}
catch (SomeSpecificException ex)
{
    // Code to handle the exception goes here.
}
finally
{
    // Code to execute after the try (and possibly catch) blocks 
    // goes here.
}

一般来说,不会在 try 里面直接写 throw XXX, 而是由try所调用的函数抛出异常,然后catch处理。CLR 会展开堆栈,同时针对特定异常类型查找包含 catch 代码块的方法,并执行找到的首个此类 catch 代码块。 如果在调用堆栈中找不到相应的 catch 代码块,将会终止进程并向用户显示消息。如果引发异常的语句不在 try 块内或者包含该语句的 try 块没有匹配的 catch 块,则运行时将检查调用方法中是否有 try 语句和 catch 块。 运行时将继续调用堆栈,搜索兼容的 catch 块。 在找到并执行 catch 块之后,控制权将传递给 catch 块之后的下一个语句。

一个 try 语句可包含多个 catch 块。 将执行第一个能够处理该异常的 catch 语句;将忽略任何后续的 catch 语句,即使它们是兼容的也是如此。 因此,应始终按照从最具有针对性(或派生程序最高)到最不具有针对性的顺序来捕获块。 例如:
static void TestCatch2()
{
    System.IO.StreamWriter sw = null;
    try
    {
        sw = new System.IO.StreamWriter(@"C:\test\test.txt");
        sw.WriteLine("Hello");
    }

    catch (System.IO.FileNotFoundException ex)
    {
        // Put the more specific exception first.
        System.Console.WriteLine(ex.ToString());  
    }

    catch (System.IO.IOException ex)
    {
        // Put the less specific exception last.
        System.Console.WriteLine(ex.ToString());  
    }
    finally 
    {
        sw.Close();
    }

    System.Console.WriteLine("Done"); 
}

异常的捕获和传播

程序引发异常后,程序的控制权将在异常处理结构中转移(沿着调用堆栈),直到找到一个能够处理改异常的catch块,否则程序终止运行,如果一个异常没有被catch,最终会导致整个程序终止。这个过程叫做异常传播,步骤为: 
  1. 如果引发异常的异常处理结构中有对应的能处理该异常的 catch 块,那么控制权移交给第一个能处理这个异常的catch块,忽略之下的catch块,异常传播结束。
  2. 如果没有找到能够处理改异常的catch语句,则程序通过当前的异常处理结构,并执行 finally块(如果有的话)。
  3. 程序到达更外一层的异常处理结构,则转到第(1)步。(即调用栈的上一层)这样的循环直到Main方法。
  4. 如果程序在当前方法中没有得到处理,则当前方法执行被终止;如果当前方法是程序Main方法,则程序执行结束。
  5. 否则,程序控制权移交给调用当前方法的代码,重复步骤(1)。
using System;
class Program
{
    static void Main(String[] args)
    {
        try
        {
            int y = ReadRecip();
            Console.WriteLine(y + 100);
        }
        catch(Exception e)
        {
            Console.WriteLine("Main() 异常:{0}", e.GetType());
        }
    }

    static int ReadRecip()
    {
        try
        {
            int x = ReadInt();
            return 100 / x;
        }
        catch(DivideByZeroException e)
        {
            Console.WriteLine("ReadRecip() 异常:{0}", e.GetType());
            return 0; 
        }
        finally
        {
            Console.WriteLine("ReadRecipe() finally");
        }
    }

    static int ReadInt()
    {
        Console.Write("Input number: ");
        return int.Parse(Console.ReadLine());
    }
}

如果输入一个字符而不是数字,则ReadInt内会引发 FormatException 异常,由于ReadRecipe无法捕获 FormatException 异常,所以会继续向调用栈的上一层转移控制权,终于在Main方法的异常处理快中,可以由 Exception 匹配,在此 catch 块中处理。
输入: 
a
结果:
Input number: a
ReadRecipe() finally
Main() 异常:System.FormatException

异常信息

C#中,所有异常都继承自 Exception 类。Exception类有以下属性,能够获得异常对象的基本信息: 
  • Message 描述异常信息的字符串
  • Source 引发异常的程序或对象的名称
  • StackTrace 对异常传播的方法调用堆栈的描述
  • TargetSite 引发异常的方法
using System;
class Program
{
    static void Main(String[] args)
    {
        try
        {
            int y = ReadRecip();
            Console.WriteLine(y + 100);
        }
        catch(Exception e)
        {
            Console.WriteLine("异常类型:{0}", e.GetType());
            Console.WriteLine("引发程序:{0}", e.Source);
            Console.WriteLine("引发方法:{0}", e.TargetSite);
            Console.WriteLine("异常信息:{0}", e.Message);
            Console.WriteLine("调用堆栈:{0}", e.StackTrace);
        }
    }

    static int ReadRecip()
    {
        int x = ReadInt();
        return 100 / x;
    }

    static int ReadInt()
    {
        Console.Write("Input number: ");
        return int.Parse(Console.ReadLine());
    }
}
输入:
0
输出:
Input number: 0
异常类型:System.DivideByZeroException
引发程序:C_Sharp_study
引发方法:Int32 ReadRecip()
异常信息:尝试除以零。
调用堆栈:   在 Program.ReadRecip() 位置 c:\users\chai\documents\visual studio 2015\Projects\C_Sharp_study\C_Sharp_study\
Program.cs:行号 24
   在 Program.Main(String[] args) 位置 c:\users\chai\documents\visual studio 2015\Projects\C_Sharp_study\C_Sharp_study\P
rogram.cs:行号 8

.NET 类库中的常用异常类

 
例外 描述
ArithmeticException 算术运算期间出现的异常的基类,例如 DivideByZeroException 和 OverflowException
ArrayTypeMismatchException 由于元素的实际类型与数组的实际类型不兼容而导致数组无法存储给定元素时引发。
DivideByZeroException 尝试将整数值除以零时引发。
IndexOutOfRangeException 索引小于零或超出数组边界时,尝试对数组编制索引时引发。
InvalidCastException 从基类型显式转换为接口或派生类型在运行时失败时引发。
NullReferenceException 尝试引用值为 null 的对象时引发。
OutOfMemoryException 尝试使用运算符分配内存失败时引发。 这表示可用于公共语言运行时的内存已用尽。
OverflowException checked 上下文中的算术运算溢出时引发。
StackOverflowException 执行堆栈由于有过多挂起的方法调用而用尽时引发;通常表示非常深的递归或无限递归。
TypeInitializationException 静态构造函数引发异常并且没有兼容的 catch 子句来捕获异常时引发。

自定义异常

程序可以引发 System 命名空间中的预定义异常类(前面提到的情况除外),或通过从 Exception 派生来创建其自己的异常类。由程序员主动引发异常时,使用自定义的异常类。自定义的异常类,继承自 System.Exception 类或者它的派生类。派生类应至少定义四个构造函数: 一个默认构造函数一个用于设置消息属性的构造函数以及一个用于设置 Message 和 InnerException 属性的构造函数。 只有当新属性提供的数据有助于解决异常时,才应将其添加到异常类中。 如果将新属性添加到派生异常类中,则应替代 ToString() 以返回添加的信息。自定义异常的模板:
[Serializable()]  // 用来序列化异常
public class InvalidDepartmentException : System.Exception
{
    public InvalidDepartmentException() : base() { }
    public InvalidDepartmentException(string message) : base(message) { }
    public InvalidDepartmentException(string message, System.Exception inner) : base(message, inner) { }

    // A constructor is needed for serialization when an
    // exception propagates from a remoting server to the client. 
    protected InvalidDepartmentException(System.Runtime.Serialization.SerializationInfo info,
        System.Runtime.Serialization.StreamingContext context) { }
}
第四个构造函数用来序列化异常。使用方法: 
throw new MyException(); 

什么时候使用自定义的异常?

由于System 命名空间中的异常只能描述有限的异常情况,如果程序逻辑要求某种错误的输入也引发异常,就需要使用异常类。比如在构造一个学生类对象时,如果输入的学生年龄是非正数,就抛出异常。 使用自定义异常可以提高程序的可靠性。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值