异常是在程序执行期间出现的问题。C# 中的异常是对程序运行时出现的特殊情况的一种响应,比如尝试除以零。
异常提供了一种把程序控制权从某个部分转移到另一个部分的方式。C# 异常处理时建立在四个关键词之上的:try、catch、finally 和 throw。
假设一个块将出现异常,一个方法使用 try 和 catch 关键字捕获异常。try/catch 块内的代码为受保护的代码,使用 try/catch 语法如下所示:
try
{
// 引起异常的语句
}
catch( ExceptionName e1 )
{
// 错误处理代码
}
catch( ExceptionName e2 )
{
// 错误处理代码
}
catch(Exception ex) //捕获所有
{
// 错误处理代码
}
finally
{
// 要执行的语句
}
如果您输入非数字字符,以下内容可能会引发异常。 System.FormatException:“Input string was not in a correct format.”
static void Main(string[] args)
{
Console.WriteLine("请输入一个数字 然后回车: ");
var num = int.Parse(Console.ReadLine());
Console.WriteLine($"{num * num} = {num} * {num}");
}
现在我加入异常处理
static void Main(string[] args)
{
Console.WriteLine("请输入一个数字 然后回车: ");
try
{
var num = int.Parse(Console.ReadLine());
Console.WriteLine($"{num * num} = {num} * {num}");
}
catch (System.FormatException ex)
{
Console.WriteLine("出错了" + ex.ToString());
}
finally
{
Console.WriteLine("我肯定会被执行");
}
}
上面的程序当用户输入的是非数字字符的时候。在int.Parse这行会抛出FormatException: 然后程序会跳到catch (System.FormatException ex)里面来。 就会执行 Console.WriteLine("出错了" + ex.ToString()); 当catch的东西执行完之就会继续执行finally里面的代码. finally的代码不管错没错都会执行到。
为了让用户在出错的时候有机会再次输入,我们还得改一下。
static void Main(string[] args)
{
while (true)
{
try
{
Console.WriteLine("请输入一个数字 然后回车: ");
var num = int.Parse(Console.ReadLine());
Console.WriteLine($"{num * num} = {num} * {num}");
break;
}
catch (System.FormatException ex)
{
Console.WriteLine("出错了" + ex.ToString());
}
finally
{
Console.WriteLine("我肯定会被执行");
}
}
}
一个函数或一段代码有时候会抛出不同的异常。如果我们想对每个异常单独处理,我们就可以写多个catch块,这个称为异常过滤器。
static void Main(string[] args)
{
while (true)
{
try
{
Console.WriteLine("请输入一个除数");
int num = int.Parse(Console.ReadLine());
int result = 100 / num;
Console.WriteLine("100 / {0} = {1}", num, result);
break;
}
catch (DivideByZeroException ex)
{
Console.WriteLine("不能除于0.");
}
catch (FormatException ex)
{
Console.WriteLine("不是数字格式. 请重试");
}
catch (Exception ex)
{
Console.WriteLine($"{ex.Message}! 请重试");
}
}
}
上面的代码中,每一个异常我们都显示不同的错误消息给用户。 注意。不允许具有相同异常类型的多个 catch 块。 捕获 Exception 类型的 catch 块必须是最后一个块, 不然所有的异常都会被它给捕获了。(IDE也会给出错误提示)
同一个 try-catch 语句中不允许使用无参数 catch 块和带有 Exception 参数的 catch 块,因为它们都做同样的事情。
try
{
//可能引发异常的代码
}
catch //不能同时有catch和catch(Exception ex)
{
Console.WriteLine("发生异常");
}
catch (Exception ex) //不能同时有catch和catch(Exception ex)
{
Console.WriteLine("发生异常");
}
无参数 catch 块 catch 或一般的 catch 块 catch(Exception ex) 必须是最后一个块。 如果在 catch 或 catch(Exception ex) 块之后还有其他 catch 块,编译器将给出错误。
try
{
//可能引发异常的代码
}
catch
{
// 这个catch块必须是最后一个块
}
catch (NullReferenceException nullEx)
{
Console.WriteLine(nullEx.Message);
}
catch (InvalidCastException inEx)
{
Console.WriteLine(inEx.Message);
}
finally 块是一个可选块,应该在 try 或 catch 块之后。 无论是否发生异常,finally 块都将始终执行。 finally 块通常用于清理代码,例如处理非托管对象。
using System;
using System.IO;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
// 这个类是在System.IO下面。我们上面有用了 using System.IO
StreamWriter streamWriter = null;
try
{
Console.Write("请输入一个文件地址: ");
string filePath = Console.ReadLine();
streamWriter = new StreamWriter(filePath);
streamWriter.WriteLine("hello");
}
catch (Exception ex)
{
Console.WriteLine("Error occurred: {0}", ex.Message);
}
finally
{
// 释放文件
if (streamWriter != null)
{
streamWriter.Dispose();
}
}
}
}
}
注意 不允许添加多个 finally 块。 此外,finally 块也不能有 return、continue 或 break 关键字。这个跟 finally 块设计不符合。
为了确保释放资源我们通常可以使用using 如下
static void Main(string[] args)
{
Console.Write("请输入一个文件地址: ");
string filePath = Console.ReadLine();
using (var streamWriter = new StreamWriter(filePath))
{
streamWriter.WriteLine("hello");
//这边有发生异常的话文件也会被关闭。
}
}
C# 允许嵌套的 try-catch 块。 使用嵌套的 try-catch 块时,异常发生时, try 块之后的第一个匹配的 catch 块会捕获到这个异常。
static void Main(string[] args)
{
var divider = 0;
try
{
try
{
var result = 100 / divider;
}
catch
{
Console.WriteLine("内部 catch"); //输出 内部 catch
}
}
catch
{
Console.WriteLine("外部 catch");
}
}
在上面的例子中将执行一个内部 catch 块,因为它是第一个处理所有异常类型的 catch 块。
如果里面的try catch没有捕获到这个异常,则这个异常会继续往外部,直到找到合适的异常过滤器。 考虑以下示例。
static void Main(string[] args)
{
var divider = 0;
try
{
try
{
var result = 100/divider;
}
catch(NullReferenceException ex)
{
Console.WriteLine("内部 catch");
}
}
catch
{
Console.WriteLine("外部 catch"); //输出 外部 catch
}
}
在上面的示例中,将引发 DivideByZeroException 类型的异常。 因为内部 catch 块只捕获 NullReferenceTypeException,所以它会被外部 catch 块捕获到。