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}"); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律