随着程序复杂度的增加,程序在执行过程中发生奇怪事情的可能性会越来越高。
C# 和 .NET CLR 使用异常来表明在程序执行期间发生的一种错误。错误通常是意料之外的问题。
出现异常的原因是多种多样。而我们每次在开发应用程序时,都会认为自己写的程序一定会没有问题,但实时是最后都发生了问题。
我们处理任何运行时发生的错误称为异常处理。
一个健壮的程序,在异常发生时应使用异常处理逻辑显示的去处理它们,以维持应用程序的正常流程。这是编码的一个良好习惯,当然,前提是你得正确的处理异常。
C# 中的异常类
C# 异常由类表示。C# 中的异常类主要直接或间接派生自 System.Exception
类。从 System.Exception
类派生的一些异常类有 System.ApplicationException
和 System.SystemException
类。
System.ApplicationException
类支持应用程序生成的异常。不建议从该类派生自定议异常,除非打算重新引发原始异常。
System.SystemException
类是所有预定义系统异常的基类。因为该类充当各种异常类型的基类,所以也不建议代码抛出该类的异常,也不应处理该类的异常,除非打算重新抛出原始异常。
提供一些从 System.SystemException
类派生的预定义异常类:
System.NullReferenceException
- 处理因引用空对象而产生的错误。
System.IndexOutOfRangeException
- 处理方法引用超出范围的数组索引时生成的错误。
System.OutOfMemoryException
- 处理因可用内存不足而产生的错误。
System.StackOverflowException
- 处理堆栈溢出产生的错误。
System.ArgumentException
- 处理提供给方法的参数之一无效生产的错误。
System.IO.IOException
- 处理 I/O 错误。
System.InvalidCastException
- 处理类型转换期间生成的错误。
System.DivideByZeroException
- 处理因除以零而产生的错误。
System.ArrayTypeMismatchException
- 处理类型与数组类型不匹配时生成的错误。
C# 中处理异常
C# 异常处理基于四个关键字:try
、catch
、finally
、throw
。
try
- try
块标识用于激活特定异常的代码块。后面可以跟一个或多个 catch
块。
catch
- 表示异常被捕获,将执行 catch
块。可以在这处理异常、记录或忽略异常。如果有多个 catch
块时,则不允许有相同的异常类型。且 Exception
类型必须放在最后一个 catch
块。
finally
- finally
块允许是否抛出异常时执行某些代码。需注意的是:finally
块是可选的,且不允许有多个 finally
块。此外,finally
块不能有 return
、continue
或 break
关键,它不会让控制流离开 finally
块。
throw
- 抛出异常,或者重新抛出异常。
static void Main(string[] args)
{
Console.WriteLine(Func());
Console.ReadKey();
}
private static string Func()
{
try
{
return "执行 catch 块语句。";
//可能导致异常的代码
}
catch (ArgumentNullException e1)
{
return e1.Message;
//处理异常代码
}
catch (IndexOutOfRangeException e2)
{
return e2.Message;
//处理异常代码
}
catch (Exception e3)
{
return e3.Message;
//处理异常代码
}
finally
{
Console.WriteLine("执行 finally 块语句。");
// 要执行的语句,在return 前执行
}
}
执行 finally 块语句。
执行 catch 块语句。
异常过滤器
在 C# 6.0 中引入了一个新功能来处理称为异常过滤器的异常。此功能将允许在 catch 块中处理更具体的异常,以便可以编写特定于异常条件的代码。
static void Main(string[] args)
{
try
{
//可能导致异常的代码
}
catch (Exception e1) when (e1 is ArgumentException) //when (过滤条件表达式)
{
//处理异常代码
}
Console.ReadKey();
}
自定义的异常
除了系统预定义的异常类型外,我们可以创建自己的异常类型。
当程序中要捕获特定类型的异常并以不同方式处理它时,创建自己的 C# 自定义异常才能体现出异常的真正用处。它有助于跟踪非常关键的非特定类型的异常。可以更轻松地监控应用程序错误并使用错误监控工具记录它。
使用自定义异常类型,我们可以编写特殊代码来处理该异常,还可以监视我们的应用程序以查找特定类型的异常,并在发生异常时通知相关人员。
自定义 C# 异常类型的好处:
-
调用代码可以对自定义异常类型进行自定义处理
-
能够围绕该自定义异常类型进行自定义监控
class Program
{
static void Main(string[] args)
{
try
{
//抛出自定义异常
throw new CustomException("自定义异常");
}
catch (Exception ex)
{
Console.WriteLine("捕获异常信息:" + ex.ToString());
}
}
}
class CustomException : Exception
{
//继承基类异常
public CustomException(string message)
{
}
}
C# 异常记录实践
正确的异常处理对于任何应用程序都至关重要。我们要尽可能仅捕获有必要的代码片段,尽量不要一个大的 try 包住整段代码。
当我们程序出现反应变慢、吞吐量下降,可以检查发生最频繁的Exception
,当然这只是提供其中一种排查方法。
在日常开发中会将异常记录到日志库中,以便查看发生的异常信息。大多使用的组件:NLog
、Serilog
或 log4net
。这三个都能够将异常记录到文件中。也允许将日志发送到其它目标,如:数据库、Windows
事件查看器、电子邮件或错误监控服务。
应将每个异常都记录下来,这对于发现代码中的问题至关重要。当然,能够尽可能记录上下文详细信息也有助于我们排查异常。比如:用户、关键变量等。
将异常记录到文件中是一个很好的实践,但还是不够,一旦在生产环境中,我们不可能每天登录每台服务器去查看日志文件。这会使得日志文件变成黑洞。
这时错误监控服务提供了一个重要工具。它允许在一个中心位置收集所有的异常。
-
集中式异常记录
-
查看和搜索所有服务器和应用程序中的所有异常
-
唯一标识异常
-
接收有关新异常或高错误率的电子邮件警报
最后,祝大家学习愉快!