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}");
}
}
}