C#学习笔记 —— 异常

异常

1、什么是异常

异常处理的目标是通过以下一个或多个操作来响应异常

  • 纠正

  • 记录异常

  • 清理外部资源

  • 向用户提示友好信息

2、try

  • try用来指明为避免出现异常而被保护的代码段, 并在发生异常时提供代码来处理

  • try块包含为避免出现异常而被保护的代码

  • catch含有一个或多个catch, 处理异常的代码段, 他们也称为异常处理程序

  • finally块含有在所有情况下都需要被执行的代码

3、异常类

  • BCL定义许多异常类, 每一个类代表一种指定的异常

  • 发生一个异常, CLR创建该类型的异常对象并寻找适当的catch子句处理

  • 所有异常都派生自System.Exception

  • 异常对象含有只读属性, 带有导致该异常的异常信息

  • 这一信息有助于调试

属性 类型 描述
Message string 这个属性含有解释异常原因的错误信息
StackTrace string 描述异常发生在何处的信息
InnerException Exception 如果当前异常是由另一个异常引起的, 则这个属性包含前一个异常的引用
Source string 如果没有被应用程序定义的异常设定, 那么这个属性含有异常所在的程序集名称

4、catch子句

有四种形式, 允许不同级别的处理

1.一般catch
  • 在catch关键字之后没有参数列表

  • 匹配try块中抛出的任何类型的异常

  • 能接受任何异常, 但不能确定引发异常的异常类型, 这只能对任何可能发生的异常进行普通处理和清理

catch
{
    statements;
}
2.特定catch
  • 以一个异常类的名称作为单一参数

  • 匹配任何指定类型的异常

  • 把一个异常类的名称当作参数, 匹配该指定类或派生自他的异常类的异常

catch(ExceptionType)
{
    statements;
}
3.带对象特定catch
  • 在异常类名称之后包括一个标识符

  • 该标识符在catch子句块中相当于一个局部变量, 被称为异常变量

  • 异常变量引用异常对象, 并能用于访问关于该对象的信息

  • 匹配该指定类或派生自他的异常类的异常

  • 还给出一个对CLR创建的异常对象的引用(通过将其付给异常变量)

  • 以在catch子句块访问异常变量的属性, 以获取关于抛出异常的详细信息

catch(ExceptionType ExceptionVar)
{
    statements;
}
4.带谓词的特定catch
  • 只有当谓词的计算结果为true才能进入

  • 其余同上

catch(ExceptionType ExceptionVar)
when(predicate){
    statements;
}
例2
int x = 10;
try
{
    int y = 0;
    x /= y; //抛异常
}
catch (DivideByZeroException e)
{
    Console.WriteLine($"1、{e.Message}");  //报错信息
    Console.WriteLine($"2、{e.Source}");  //错误源名称空间
    Console.WriteLine($"3、{e.StackTrace}");   //错误位置
}

5、异常过滤器

  • 一个异常类型可以有多个处理程序, 而不必由一个处理程序处理这个异常类型的所有可能异常

  • 在catch子句中, 在满足异常处理器情况下, 异常对象被传递给处理程序

try
{
    //xxx
}
catch (HttpRequestException e) when (e.Message.Contains("307"))
{
    //xxx
}
catch (HttpRequestException e) when (e.Message.Contains("301"))
{
    //xxx
}
when子句的重要属性
  • 必须包含谓词表达式, 该表达式返回值非真即假

  • 不能是异步的

  • 不应该使用任何需要长时间运行的操作

  • 谓词表达式中发生的任何异常都会被忽略

    • 谓词表达式保留调试原始程序错误所需的信息

6、catch子句段

  • 如果catch子句接受一个参数, 那么系统会把这个异常变量设置为对异常对象的引用, 这样就可以检查他以确定异常的原因

  • 如果异常是前一个异常引起的, 可以通过异常变量的InnerException属性来获取对前一个异常的引用

  • 允许有很多catch子句, 但是一般catch只能有一个

当异常发生时, 系统按顺序搜索catch子句的列表, 第一个匹配该异常对象类型的catch子句被执行, 因此catch子句的排序有两个重要规则

  • 特定catch子句必须以一种顺序排列

    • 最特定异常类型第一, 最普通的类型排最后

      • 即子类异常排在父类异常之前

    • 如果有一个一般catch子句, 他必须是最后一个, 并且在所有特定catch子句之后

    • 不推荐使用一般catch子句, 因为当代码应该以特定方式处理错误的时候, 他允许程序继续执行从而隐藏了错误

7、finally

  • 如果try块内没异常, 在try块的结尾, 控制流跳过任何catch子句并到finally块

  • 如果try块内有异常, 那么在catch子句段中适当的catch子句被执行, 接着执行finally

8、为异常寻找处理程序

  • 如果在try块内发生异常, 系统会查看是否有任何一个catch子句能处理该异常

  • 如果找到了适当的catch子句, 会发生如下3项中的1项

    • 该catch子句被执行

    • 如果有finally块, 那么他被执行

    • 执行在try语句的尾部之后继续

      • 在finally块之后

      • 无finally就在最后一个catch子句之后

9、进一步搜索

  • 如果异常在一个没有被try保护的代码中抛出, 或者没有匹配的异常处理程序

  • 系统将按顺序搜索调用栈, 查看是否存在带匹配的处理程序封装try块

(1)一般法则

(2)例子

static void Main(string[] args)
{
    Progrom2309 utils = new Progrom2309();
    try
    {
        utils.A();
    }
    catch (DivideByZeroException)
    {
        Console.WriteLine("catch main");
    }
    finally
    {
        Console.WriteLine("finally main");
    }
    Console.WriteLine("main running"); 
    //finally b, finally a, catch main, finally main, main running
}
class Progrom2309
{
    public void A()
    {
        try
        {
            B();
        }
        catch(IndexOutOfRangeException)
        {
            Console.WriteLine("catch a");
        }finally
        {
            Console.WriteLine("finally a");
        }
    }
    void B()
    {
        int x = 10;
        int y = 0;
        try
        {
            x /= y;
        }
        catch(IndexOutOfRangeException)
        {
            Console.WriteLine("catch b");
        }finally
        {
            Console.WriteLine("finally b");
        }
    }
}

10、抛出异常

throw ExceptionType;

11、不带异常对象的抛出 -- 重新抛出异常

  • throw语句还可以在catch块内部不带异常对象使用

    • 这种形式重新抛出当前异常, 系统继续搜索, 为该异常寻找另外的处理程序

    • 这种形式只能用在catch语句内部

public static void PrintArg(string arg)
{
    try
    {
        try
        {
            if(arg == null)
            {
                ArgumentException myEx  
                    = new ArgumentException();
                throw myEx;
            }
            Console.WriteLine(arg);
        }
        catch (ArgumentException e)
        {
            Console.WriteLine($"{e.Message}");
            throw; //重新抛出异常没有附加参数
        }
    }
    catch
    {
        Console.WriteLine("outer catch an Exception");
    }
}
static void Main(string[] args)
{
    PrintArg(null); //参数不符合规范 outer catch an Exception
    PrintArg("12312"); //12312
}

12、throw表达式

  • 代码中有些地方不允许使用表达式

  • throw表达式和throw语句相同, 无序指定其中一个, 当编译器发现它需要throw表达式时, 就会使用一个

  • 空接合运算符是由两个??分隔的操作数组成的

    • 代表第一个操作数为空, 则使用第二个

private int mSecurityCode;
public int SecurityCode
{
    get => mSecurityCode;
    //把throw语句作为·空接合运算符作为第二个操作数·
    set => mSecurityCode = value ?? throw new ArugumentNullException("安全码不能为空");
}

三元表达式中使用throw表达式

class Program2312
{
    public static string SecretCode { get { return "Roses are red"; } }
    static void Main()
    {
        bool safe = false;
        try
        {
            string secretCode = safe ? SecretCode : throw new Exception("not safe now");
            Console.WriteLine($"code: {SecretCode}");
        }
        catch (Exception e)
        {
            Console.WriteLine($"{e.Message}");
        }
    }
}

posted on 2023-07-24 22:32  老菜农  阅读(18)  评论(0编辑  收藏  举报

导航