C#抛出异常详解(附带实例)
C# 代码在运行时或在用户代码中都可以抛出异常。以下例子中,Display 方法会抛出 System.ArgumentNullException:
检查参数是否为 null 并适时抛出 ArgumentNullException 异常是再平常不过的操作了,因此在 .NET 6 中我们可以将其简写为:
throw 表达式也可以出现在三元条件表达式中:
异常被捕获后可以再次抛出,例如:
重新抛出异常可用于需要记录错误但是并不将异常隐藏的情形,也可以在异常超出处理范围的情况下放弃对异常进行处理。另一种常见情形是重新抛出某个类型更加具体的异常:
在跨越信任边界时,常见做法是重新抛出一个不那么明确的异常,以防止因技术信息泄露而给黑客可乘之机。
所有的 C# 异常都是运行时异常,没有和 Java 对等的编译时 checked 异常(checked exception)。
以下所列的异常类型在 CLR 和 .NET 库中广泛使用,可以在程序中抛出这些异常或者将其作为基类型来派生自定义的异常类型:
另一个常见的异常类型是 NullReferenceException。当访问 null 对象的成员时,CLR 就会抛出这个异常(表示代码有缺陷)。使用下面的语句会直接抛出一个 NullRe-ferenceException 异常(仅用于测试目的):
int 类型是一个典型的例子,它为 Parse 方法定义了两个版本:
可以用如下方式令 XXX 方法调用 TryXXX 方法来实现这种模式:
try { Display(null); } catch (ArgumentNullException ex) { Console.WriteLine("Caught the exception"); } void Display(string name) { if (name == null) throw new ArgumentNullException(nameof(name)); Console.WriteLine(name); }
检查参数是否为 null 并适时抛出 ArgumentNullException 异常是再平常不过的操作了,因此在 .NET 6 中我们可以将其简写为:
void Display(string name) { ArgumentNullException.ThrowIfNull(name); Console.WriteLine(name); }请注意,我们无须指定参数的名称。
C# throw表达式
throw 也可以以表达式的形式出现在表达式体函数中:public string Foo() => throw new NotImplementedException();
throw 表达式也可以出现在三元条件表达式中:
string ProperCase(string value) => value == null ? throw new ArgumentException("value") : value == "" ? "" : char.ToUpper(value[0]) + value.Substring(1);
异常被捕获后可以再次抛出,例如:
try { // ... } catch (Exception ex) { // Log error throw; // Rethrow same exception }如果将 throw 替换为 throw ex,那么这个例子仍然有效。但是新产生异常的 StackTrace 属性不再反映原始的错误。
重新抛出异常可用于需要记录错误但是并不将异常隐藏的情形,也可以在异常超出处理范围的情况下放弃对异常进行处理。另一种常见情形是重新抛出某个类型更加具体的异常:
try { // Parse a DateTime from XML element data } catch (FormatException ex) { throw new XmlException("Invalid DateTime", ex); }请注意,当构建 XmlException 时,我们将原始的异常 ex 作为第二个参数。这个参数将作为新异常的 InnerException 属性而辅助诊断。几乎所有类型的异常都提供了类似的构造器。
在跨越信任边界时,常见做法是重新抛出一个不那么明确的异常,以防止因技术信息泄露而给黑客可乘之机。
C# System.Exception的关键属性
System.Exception 类有下面几个重要属性:- StackTrace:表示一个异常从起源到 catch 语句块的所有调用方法的字符串。
- Message:描述异常的字符串。
- InnerException:导致外部异常的内部异常(如果有的话)。而内部异常本身也可以有另外一个 InnerException。
所有的 C# 异常都是运行时异常,没有和 Java 对等的编译时 checked 异常(checked exception)。
以下所列的异常类型在 CLR 和 .NET 库中广泛使用,可以在程序中抛出这些异常或者将其作为基类型来派生自定义的异常类型:
异常类型 | 说明 |
---|---|
System.ArgumentException | 当使用不恰当的参数调用函数时抛出该异常。这通常表明应用程序有缺陷。 |
System.ArgumentNullException | ArgumentException 的子类。当函数的参数(意外)为 null 时抛出该异常。 |
System.ArgumentOutOfRangeException | ArgumentException 的子类。当(通常是数字)参数太大或者太小时抛出该异常。例如,当向只能接受正数的函数传递负数时会抛出该异常。 |
System.InvalidOperationException | 无论参数值如何,当对象的状态无法令方法成功执行时抛出该异常。例如,读取未打开的文件或在列表对象已修改的情况下用枚举器访问下一个元素时会抛出该异常。 |
System.NotSupportedException | 当不支持特定的功能时抛出该异常。例如,当在一个 IsReadOnly 为 true 的集合上调用 Add 方法时会抛出该异常。 |
System.NotImplementedException | 当特定的函数还没有实现时抛出该异常。 |
System.ObjectDisposedException | 当函数调用的对象已被销毁时抛出该异常。 |
另一个常见的异常类型是 NullReferenceException。当访问 null 对象的成员时,CLR 就会抛出这个异常(表示代码有缺陷)。使用下面的语句会直接抛出一个 NullRe-ferenceException 异常(仅用于测试目的):
throw null;
TryXXX方法模式
当编写方法时需要考虑方法出错时的行为,可以返回某个特定的错误代码,或抛出一个异常。一般情况下,如果错误发生在正常的工作流程之外,或者方法的直接调用者很可能无法处理这个错误时选择抛出异常。但是有些情况下最好给调用者提供两种选择。int 类型是一个典型的例子,它为 Parse 方法定义了两个版本:
public int Parse(string input); public bool TryParse(string input, out int returnValue);如果解析失败,则 Parse 方法抛出一个异常,而 TryParse 方法则返回 false。
可以用如下方式令 XXX 方法调用 TryXXX 方法来实现这种模式:
public return-type Xxx(input-type input) { return-type returnValue; if (!TryXxx(input, out returnValue)) throw new YYYException(...); return returnValue; }