异常用于指示在运行程序时发生了错误。 此时将创建一个描述错误的异常对象,然后使用 throw
。 然后,运行时搜索最兼容的异常处理程序。
当存在下列一种或多种情况时,程序员应引发异常:
该方法无法完成其定义的功能。 例如,如果方法的参数具有无效值:
static void CopyObject(SampleClass original) { _ = original ?? throw new ArgumentException("Parameter cannot be null", nameof(original)); }
根据对象的状态,对某个对象进行不适当的调用。 一个示例可能是尝试写入只读文件。 如果对象的状态不允许操作,则应抛出 InvalidOperationException 的实例或基于此类派生的对象实例。 以下代码是引发 InvalidOperationException 对象的方法的示例:
public class ProgramLog { FileStream logFile = null!; public void OpenLog(FileInfo fileName, FileMode mode) { } public void WriteLog() { if (!logFile.CanWrite) { throw new InvalidOperationException("Logfile cannot be read-only"); } // Else write data to the log and return. } }
当方法的参数导致异常时。 在这种情况下,应捕获原始异常,并创建实例 ArgumentException 。 原始异常应作为ArgumentException参数传递给InnerException的构造函数。
static int GetValueFromArray(int[] array, int index) { try { return array[index]; } catch (IndexOutOfRangeException e) { throw new ArgumentOutOfRangeException( "Parameter index is out of range.", e); } }
注释
前面的示例演示如何使用该
InnerException
属性。 这是有意简化的。 在实践中,应先检查索引是否在范围内,然后再使用它。 当参数成员引发在调用成员之前无法预料到的异常时,可以使用此方法来包装异常。
异常包含一个名为 StackTrace 的属性。 此字符串包含当前调用堆栈上的方法的名称,以及为每个方法引发异常的文件名和行号。 StackTrace对象由公共语言运行时(CLR)从throw
语句的起始位置自动创建,因此异常必须在堆栈跟踪应该开始的地方被抛出。
所有异常都包含一个名为Message的属性。 应将此字符串设置为解释异常的原因。 不应将对安全性敏感的信息放入消息文本中。 除了 Message 外,ArgumentException 还有一个名为 ParamName 的属性,该属性应设置为导致引发异常的参数名称。 在属性 setter 中, ParamName 应设置为 value
.
只要公共和受保护的方法无法完成预期函数,就会引发异常。 引发的异常类是符合错误条件的最特定可用异常。 这些异常应记录为类功能的一部分,对原始类的派生类或更新应保留相同的行为以实现向后兼容性。
引发异常时应避免的情况
以下列表标识了在抛出异常时应避免的做法:
- 不要使用异常来更改程序流作为普通执行的一部分。 使用异常来报告和处理错误状况。
- 不应将异常作为返回值或参数返回,而应该引发异常。
- 不要从自己的源代码中故意抛出System.Exception、System.SystemException、System.NullReferenceException或System.IndexOutOfRangeException。
- 不要创建可以在调试模式下引发但不能在发布模式下引发的异常。 若要在开发阶段确定运行时错误,请改用调试断言。
任务返回方法中的异常
使用 async
修饰符声明的方法在出现异常时有一些特殊注意事项。 方法 async
中引发的异常会存储在返回的任务中,直到任务即将出现时才会出现。 有关存储的异常的详细信息,请参阅 异步异常。
建议在输入方法的异步部分之前验证参数并引发任何相应的异常,例如 ArgumentException 和 ArgumentNullException。 也就是说,在开始工作之前,这些验证异常应同步出现。 以下代码片段演示了一个示例,其中,如果引发异常, ArgumentException 异常将同步出现,而 InvalidOperationException 异常将存储在返回的任务中。
// Non-async, task-returning method.
// Within this method (but outside of the local function),
// any thrown exceptions emerge synchronously.
public static Task<Toast> ToastBreadAsync(int slices, int toastTime)
{
if (slices is < 1 or > 4)
{
throw new ArgumentException(
"You must specify between 1 and 4 slices of bread.",
nameof(slices));
}
if (toastTime < 1)
{
throw new ArgumentException(
"Toast time is too short.", nameof(toastTime));
}
return ToastBreadAsyncCore(slices, toastTime);
// Local async function.
// Within this function, any thrown exceptions are stored in the task.
static async Task<Toast> ToastBreadAsyncCore(int slices, int time)
{
for (int slice = 0; slice < slices; slice++)
{
Console.WriteLine("Putting a slice of bread in the toaster");
}
// Start toasting.
await Task.Delay(time);
if (time > 2_000)
{
throw new InvalidOperationException("The toaster is on fire!");
}
Console.WriteLine("Toast is ready!");
return new Toast();
}
}
定义异常类
程序可以在命名空间中 System 引发预定义的异常类(前面未指出的情况除外),也可以通过派生自 Exception创建自己的异常类。 派生类应至少定义三个构造函数:一个无参数构造函数,一个用于设置消息属性,一个用于设置 Message 和 InnerException 属性。 例如:
[Serializable]
public class InvalidDepartmentException : Exception
{
public InvalidDepartmentException() : base() { }
public InvalidDepartmentException(string message) : base(message) { }
public InvalidDepartmentException(string message, Exception inner) : base(message, inner) { }
}
当它们提供的数据可用于解析异常时,向异常类添加新属性。 如果将新属性添加到派生异常类中,则应替代 ToString()
以返回添加的信息。
C# 语言规范
有关详细信息,请参阅 C# 语言规范中的 Exceptions 和 Throw 语句。 语言规范是 C# 语法和用法的明确来源。