了解如何在 C# 中创建和引发异常
- 16 分钟
.NET 提供派生自基类的异常类的 System.Exception 层次结构。 C# 应用程序可以创建并引发任何异常类型的异常。 开发人员还可以通过设置属性值来定制包含应用程序特定信息的异常对象。
注释
本模块重点介绍如何创建和引发异常,以及自定义异常对象。 创建自定义异常类超出了本模块的范围。
创建异常对象
从代码中创建和引发异常是 C# 编程的一个重要方面。 生成异常以响应特定条件、问题或错误有助于确保应用程序的稳定性。
创建的异常类型取决于编码问题,应尽可能匹配异常的预期用途。
例如,假设要创建一个名为 GraphData 执行数据分析的方法。 该方法接收数据数组作为输入参数。 该方法预期输入数据在特定范围内。 如果方法接收超出预期范围的数据,则会创建并引发类型 ArgumentException异常。 异常将由负责提供数据的代码在调用堆栈中的某个位置进行处理。
下面是在创建异常时可以使用的一些常见异常类型:
ArgumentException或ArgumentNullException:当使用无效参数值或 null 引用调用方法或构造函数时,请使用这些异常类型。InvalidOperationException:当方法的作条件不支持成功完成特定方法调用时,请使用此异常类型。NotSupportedException:在操作或功能不受支持时使用此异常类型。IOException:当输入/输出作失败时,请使用此异常类型。FormatException:当字符串或数据的格式不正确时,请使用此异常类型。
关键字 new 用于创建异常的实例。 例如,可以创建异常类型的实例 ArgumentException ,如下所示:
ArgumentException invalidArgumentException = new ArgumentException();
配置和引发自定义异常
引发异常对象的过程涉及创建异常派生类的实例,可以选择配置异常的属性,然后使用关键字引发对象 throw 。
在引发异常之前,使用上下文信息自定义异常通常很有帮助。 可以通过配置异常对象的属性来提供应用程序特定信息。 例如,以下代码创建一个使用自定义invalidArgumentException属性命名Message的异常对象,然后引发异常:
ArgumentException invalidArgumentException = new ArgumentException("ArgumentException: The 'GraphData' method received data outside the expected range.");
throw invalidArgumentException;
注释
异常的 Message 属性为只读。 因此,在实例化对象时必须设置自定义 Message 属性。
自定义异常对象时,请务必提供明确的错误消息来描述问题以及如何解决此问题。 还可以包含其他信息,例如堆栈跟踪和错误代码,以帮助用户更正问题。
还可以直接在语句中创建 throw 异常对象。 例如:
throw new FormatException("FormatException: Calculations in process XYZ have been cancelled due to invalid data format.");
引发异常时要记住的一些注意事项包括:
Message属性应解释发生异常的原因。 但是,敏感信息或表示安全问题的信息不应放在消息文本中。- 该
StackTrace属性通常用于跟踪异常的来源。 此字符串属性包含当前调用堆栈上的方法的名称,以及与异常关联的每个方法中的文件名和行号。 公共语言运行时(CLR)会在StackTrace语句的执行点自动创建throw对象。 必须从堆栈跟踪开始的点引发异常。
何时引发异常
每当方法无法完成其预期目的时,方法都应引发异常。 引发的异常应基于符合错误条件的最具体的可用异常。
假设开发人员正在处理实现业务流程的应用程序。 业务流程依赖于用户输入。 如果输入与预期的数据类型不匹配,则实现业务流程的方法将创建并引发异常。 可以使用属性值中的应用程序特定信息配置异常对象。 以下代码示例演示了方案:
string[][] userEnteredValues = new string[][]
{
new string[] { "1", "two", "3"},
new string[] { "0", "1", "2"}
};
foreach (string[] userEntries in userEnteredValues)
{
try
{
BusinessProcess1(userEntries);
}
catch (Exception ex)
{
if (ex.StackTrace.Contains("BusinessProcess1") && (ex is FormatException))
{
Console.WriteLine(ex.Message);
}
}
}
static void BusinessProcess1(string[] userEntries)
{
int valueEntered;
foreach (string userValue in userEntries)
{
try
{
valueEntered = int.Parse(userValue);
// completes required calculations based on userValue
// ...
}
catch (FormatException)
{
FormatException invalidFormatException = new FormatException("FormatException: User input values in 'BusinessProcess1' must be valid integers");
throw invalidFormatException;
}
}
}
在此代码示例中,顶级语句调用 BusinessProcess1 该方法,并传入包含用户输入值的字符串数组。 该方法 BusinessProcess1 需要可转换为整数的用户输入值。 当方法遇到格式无效的数据时,它会使用自定义FormatException属性创建异常类型的实例Message。 然后,该方法将引发异常。 异常作为名为 ex 的对象在顶级语句中捕获。 ex在向用户显示异常消息之前,将检查对象的属性。 首先,代码检查 StackTrace 属性,以查看它是否包含“BusinessProcess1”。 其次,异常对象 ex 已验证为类型 FormatException。
重新引发异常
除了可以引发新异常之外,throw 还可以在catch代码块内部重新引发异常。 在这种情况下,throw 不接受异常操作数。
catch (Exception ex)
{
// handle or partially handle the exception
// ...
// re-throw the original exception object for further handling down the call stack
throw;
}
重新引发异常时,将使用原始异常对象,因此不会丢失有关异常的任何信息。 如果要创建包装原始异常的新异常对象,可以将原始异常作为参数传递给新异常对象的构造函数。 例如:
catch (Exception ex)
{
// handle or partially handle the exception
// ...
// create a new exception object that wraps the original exception
throw new ApplicationException("An error occurred", ex);
}
对于“BusinessProcess1”应用程序方案,请考虑以下更新:
- 方法
BusinessProcess1已更新,以包含其他详细信息。BusinessProcess1现在遇到两个问题,并且必须为每个问题生成异常。 - 顶级语句已更新。 顶级语句现在调用该方法
OperatingProcedure1。OperatingProcedure1在代码块中BusinessProcess1调用try。 - 该方法
OperatingProcedure1能够处理其中一种异常类型,并部分处理另一种异常类型。 处理部分处理的异常后,OperatingProcedure1必须重新引发原始异常。
以下代码示例演示了更新的方案:
try
{
OperatingProcedure1();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Exiting application.");
}
static void OperatingProcedure1()
{
string[][] userEnteredValues = new string[][]
{
new string[] { "1", "two", "3"},
new string[] { "0", "1", "2"}
};
foreach(string[] userEntries in userEnteredValues)
{
try
{
BusinessProcess1(userEntries);
}
catch (Exception ex)
{
if (ex.StackTrace.Contains("BusinessProcess1"))
{
if (ex is FormatException)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Corrective action taken in OperatingProcedure1");
}
else if (ex is DivideByZeroException)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Partial correction in OperatingProcedure1 - further action required");
// re-throw the original exception
throw;
}
else
{
// create a new exception object that wraps the original exception
throw new ApplicationException("An error occurred - ", ex);
}
}
}
}
}
static void BusinessProcess1(string[] userEntries)
{
int valueEntered;
foreach (string userValue in userEntries)
{
try
{
valueEntered = int.Parse(userValue);
checked
{
int calculatedValue = 4 / valueEntered;
}
}
catch (FormatException)
{
FormatException invalidFormatException = new FormatException("FormatException: User input values in 'BusinessProcess1' must be valid integers");
throw invalidFormatException;
}
catch (DivideByZeroException)
{
DivideByZeroException unexpectedDivideByZeroException = new DivideByZeroException("DivideByZeroException: Calculation in 'BusinessProcess1' encountered an unexpected divide by zero");
throw unexpectedDivideByZeroException;
}
}
}
更新的示例代码生成以下输出:
FormatException: User input values in 'BusinessProcess1' must be valid integers
Corrective action taken in OperatingProcedure1
DivideByZeroException: Calculation in 'BusinessProcess1' encountered an unexpected divide by zero
Partial correction in OperatingProcedure1 - further action required
DivideByZeroException: Calculation in 'BusinessProcess1' encountered an unexpected divide by zero
Exiting application.
引发异常时应避免的情况
以下列表标识了在抛出异常时应避免的做法:
- 不要使用异常来更改程序流作为普通执行的一部分。 使用异常来报告和处理错误状况。
- 不应将异常作为返回值或参数返回,而应该引发异常。
- 不要从自己的源代码中故意抛出
System.Exception、System.SystemException、System.NullReferenceException或System.IndexOutOfRangeException。 - 不要创建可以在调试模式下引发但不能在发布模式下引发的异常。 若要在开发阶段识别运行时错误,请改用
Debug.Assert。
注释
此方法 Debug.Assert 是用于在开发过程中捕获逻辑错误的工具。 默认情况下,该方法 Debug.Assert 仅适用于调试生成。 可以在 Debug.Assert 调试会话中使用来检查不应发生的条件。 该方法采用两个参数:要检查的布尔条件,以及一条可选的字符串消息,用于显示条件是否为 false。 Debug.Assert 不应被用来代替抛出异常,抛出异常是在正常执行代码期间处理异常情况的一种方法。 应使用 Debug.Assert 捕获不应发生的错误,并使用异常来处理在程序正常执行期间可能发生的错误。
回顾
在本单元中,应谨记以下几个重要事项:
- 创建和引发异常时,异常类型必须与异常的预期用途尽可能接近。
- 在
throw异常中,可以创建异常派生类的实例,配置其属性,然后使用throw关键字。 - 创建异常对象时,请务必提供明确的错误消息和其他信息来帮助用户更正问题。