C# try catch异常语句用法详解(附带实例)
C# 中的 try 语句是为了处理错误或者执行清理代码而定义的语句块。
try 语句块后面必须跟一个或多个 catch 语句块或 finally 语句块,或者两者都有。当 try 语句块执行发生错误时,就会执行 catch 语句块。当 try 语句块结束时(或者如果当前是 catch 语句块且当 catch 语句块结束时),不管有没有发生错误,都会执行 finally 语句块来执行清理代码。
catch 语句块可以访问 Exception 对象,该对象包含错误信息。我们可以在 catch 语句块中处理错误或者再次抛出异常。例如,记录日志并重新抛出异常,或者抛出一个更高层次的异常。
finally 语句块为程序的执行提供了确定性,CLR 会尽最大努力保证其执行。它通常用于执行清理任务,例如,关闭网络连接等。
try 语句的使用示例如下:
考虑如下程序:
我们更提倡提前进行检查以避免错误,而不是依赖 try/catch 语句块。这是因为异常处理代价比较大,通常需要超过几百个时钟周期。
当 try 语句中抛出异常时,公共语言运行时(CLR)会执行测试:
捕获 System.Exception 表示捕获所有可能的异常,通常用于如下场景:
比上述更常见的做法则是捕获特定类型的异常(例如 OutOfMemoryException),以避免出现设计中遗漏特定情景的情况。
可以使用多个 catch 子句处理多种异常类型(同样,以下例子也可以进行显式参数检查而不仅仅是进行异常处理):
如果不需要访问异常的属性,则可以捕获异常但不指定变量:
我们可以在 catch 子句中添加 when 子句来指定异常筛选器(exception filter):
如果本例中抛出了 WebException,则 when 关键字后指定的布尔表达式就会执行。如果执行结果为 false,则 catch 语句块会被忽略,继而评估后续的 catch 语句块。有了异常筛选器之后,我们就可以重复捕获同类型的异常了:
finally 语句块会在以下任一种情况后执行:
唯一能够阻止 finally 语句块执行的就只有无限循环,或者应用程序进程突然终止了。
finally 语句块为程序添加了确定性保证。在以下例子中,即使发生了列表中的情况,打开的文件也总是能够关闭:
using 语句提供了一种优雅方式,可以在 finally 块中调用 IDisposable 接口对象的 Dispose 方法。因而以下语句:
try 语句块后面必须跟一个或多个 catch 语句块或 finally 语句块,或者两者都有。当 try 语句块执行发生错误时,就会执行 catch 语句块。当 try 语句块结束时(或者如果当前是 catch 语句块且当 catch 语句块结束时),不管有没有发生错误,都会执行 finally 语句块来执行清理代码。
catch 语句块可以访问 Exception 对象,该对象包含错误信息。我们可以在 catch 语句块中处理错误或者再次抛出异常。例如,记录日志并重新抛出异常,或者抛出一个更高层次的异常。
finally 语句块为程序的执行提供了确定性,CLR 会尽最大努力保证其执行。它通常用于执行清理任务,例如,关闭网络连接等。
try 语句的使用示例如下:
try { .... // 此代码块中可能会抛出异常 } catch (ExceptionA ex) { .... // 处理类型为 ExceptionA 的异常 } catch (ExceptionB ex) { .... // 处理类型为 ExceptionB 的异常 } finally { .... // 清理代码 }
考虑如下程序:
int y = Calc (0); Console.WriteLine (y); int Calc (int x) => 10 / x;由于 x 是 0,因此运行时将抛出 DivideByZeroException,程序终止。我们可以通过 catch 捕获异常来防止程序提前终止:
try { int y = Calc (0); Console.WriteLine (y); } catch (DivideByZeroException ex) { Console.WriteLine ("x cannot be zero"); } Console.WriteLine ("program completed"); int Calc (int x) => 10 / x;输出为:
x cannot be zero
program completed
我们更提倡提前进行检查以避免错误,而不是依赖 try/catch 语句块。这是因为异常处理代价比较大,通常需要超过几百个时钟周期。
当 try 语句中抛出异常时,公共语言运行时(CLR)会执行测试:
-
try 语句是否具有兼容的 catch 语句块:
- 如果有,则执行点转移到可以处理相应异常的 catch 语句块,之后再跳转到 finally 语句块(如果有的话),再继续正常执行;
- 如果没有,则执行会直接跳转到 finally 语句块(如果有的话),之后 CLR 会从调用栈中寻找其他 try 语句块,若找到则重复上述测试。
- 如果没有任何函数处理该异常,则程序将终止执行。
catch子句
catch 子句定义捕获哪些类型的异常,这些异常应当是 System.Exception 或者 System.Exception 的子类。捕获 System.Exception 表示捕获所有可能的异常,通常用于如下场景:
- 不论何种特定类型的异常,程序都可以恢复;
- (在记录日志之后)重新抛出该异常;
- 程序终止前的最后一个错误处理函数。
比上述更常见的做法则是捕获特定类型的异常(例如 OutOfMemoryException),以避免出现设计中遗漏特定情景的情况。
可以使用多个 catch 子句处理多种异常类型(同样,以下例子也可以进行显式参数检查而不仅仅是进行异常处理):
class Test { static void Main (string[] args) { try { byte b = byte.Parse (args[0]); Console.WriteLine (b); } catch (IndexOutOfRangeException) { Console.WriteLine ("Please provide at least one argument"); } catch (FormatException) { Console.WriteLine ("That's not a number!"); } catch (OverflowException) { Console.WriteLine ("You've given me more than a byte!"); } } }一个 catch 子句只针对一种给定的异常。如果想通过捕获更普遍的异常(如 System.Exception)来构建安全网,则必须把处理特定异常的逻辑放在前面。
如果不需要访问异常的属性,则可以捕获异常但不指定变量:
catch (OverflowException) // no variable { … }甚至,可以同时忽略异常的类型和变量(捕获所有的异常):
catch { … }
我们可以在 catch 子句中添加 when 子句来指定异常筛选器(exception filter):
catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout) { … }
如果本例中抛出了 WebException,则 when 关键字后指定的布尔表达式就会执行。如果执行结果为 false,则 catch 语句块会被忽略,继而评估后续的 catch 语句块。有了异常筛选器之后,我们就可以重复捕获同类型的异常了:
catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout) { … } catch (WebException ex) when (ex.Status == WebExceptionStatus.SendFailure) { … }when 子句中的布尔表达式可以包含副作用,例如,调用一个方法来记录诊断所需的异常。
finally语句块
无论代码是否抛出异常,也无论 try 语句块是否完全执行,finally 语句块总会执行。通常,finally 语句块用于执行清理工作。finally 语句块会在以下任一种情况后执行:
- 在 catch 语句块执行完成后(或抛出一个新的异常时)。
- try 语句块执行完成后(或者抛出了一个异常但没有任何 catch 语句块针对该异常)。
- 控制逻辑使用 jump 语句(例如,return 或 goto)离开了 try 语句块。
唯一能够阻止 finally 语句块执行的就只有无限循环,或者应用程序进程突然终止了。
finally 语句块为程序添加了确定性保证。在以下例子中,即使发生了列表中的情况,打开的文件也总是能够关闭:
- try 语句块正常结束;
- 因为是空文件(EndOfStream)而提前返回了;
- 读取文件时抛出了 IOException。
void ReadFile() { StreamReader reader = null; // In System.IO namespace try { reader = File.OpenText ("file.txt"); if (reader.EndOfStream) return; Console.WriteLine (reader.ReadToEnd()); } finally { if (reader != null) reader.Dispose(); } }在本例中,我们通过 StreamReader 的 Dispose 方法来关闭文件。在 finally 语句块中调用 Dispose 方法是一种标准约定,在 C# 中也有 using 语句对此提供直接支持。
1) using语句
许多类的内部都封装了非托管资源,例如,文件句柄、图像句柄、数据库连接等。这些类都实现了 System.IDisposable 接口,该接口定义了一个名为 Dispose 的无参数方法,用于清除这些非托管资源。using 语句提供了一种优雅方式,可以在 finally 块中调用 IDisposable 接口对象的 Dispose 方法。因而以下语句:
using (StreamReader reader = File.OpenText ("file.txt")) { … }完全等价于:
{ StreamReader reader = File.OpenText ("file.txt"); try { … } finally { if (reader != null) ((IDisposable)reader).Dispose(); } }
2) using声明
如果我们忽略 using 语句后的括号和语句块,那么 using 语句就成了 using 声明(C# 8)。相应的资源会在程序执行到该声明所在语句块外时释放:if (File.Exists ("file.txt")) { using var reader = File.OpenText ("file.txt"); Console.WriteLine (reader.ReadLine()); … }在上述代码中,当程序执行到 if 语句块外时将调用 reader 对象的 Dispose 方法。