异常的常见几种类型
1. NullReferenceException:空指针引用异常
csharp
```csharp
// 场景1: 未初始化的对象
List<string> names = null;
Console.WriteLine(names.Count); // 异常: names 是 null
// 场景2: 方法返回了 null
string FindUserById(int id)
{
// 假设数据库里没找到id=999的用户
if (id == 999) return null;
return "Admin";
}
string user = FindUserById(999);
Console.WriteLine(user.ToUpper()); // 异常: user 是 null
```
- 解决方案: :
-
防御性检查 :在访问任何可能为
null的对象前,进行显式的null检查。csharpif (user != null) { Console.WriteLine(user.ToUpper()); } -
空条件运算符
?.和??:csharp// 如果 user 是 null,整个表达式直接返回 null,而不是抛出异常 string upperUser = user?.ToUpper(); // 如果 user 是 null,则使用 "Guest" 作为默认值 string displayName = user ?? "Guest"; // 组合使用 Console.WriteLine(user?.ToUpper() ?? "USER NOT FOUND"); -
可空引用类型 :通过静态分析,在编译时就警告你潜在的
NullReferenceException。csharp#nullable enable // 开启可空引用类型检查 string? user = FindUserById(999); // ? 表示 user 可以为 null Console.WriteLine(user.ToUpper()); // 编译器会在这里发出警告!
-
2. IndexOutOfRangeException:索引越界异常
-
当你试图用一个无效的索引来访问数组、列表(
List<T>)或其他基于索引的集合的元素时,此异常就会被抛出。无效索引指的是小于0,或大于等于集合的元素数量。csharpint[] scores = { 98, 76, 100 }; // 场景1: 访问不存在的索引 Console.WriteLine(scores[3]); // 有效索引是 0, 1, 2 // 场景2: 循环条件错误 for (int i = 0; i <= scores.Length; i++) { // 错误在于 i <= Length Console.WriteLine(scores[i]); // 当 i 等于 scores.Length (3) 时 BOOM! } // 场景3: 对空集合进行索引访问 var emptyList = new List<string>(); Console.WriteLine(emptyList[0]); // BOOM! -
解决方案::
-
正确的循环 :在
for循环中,永远使用<而不是<=来比较索引和集合长度。csharpfor (int i = 0; i < scores.Length; i++) { /* 安全 */ } -
优先使用
foreach:foreach循环在内部处理了迭代逻辑,完全避免了手动操作索引,从而根除了此类错误。csharpforeach (var score in scores) { /* 绝对安全 */ } -
边界检查 :在直接通过索引访问前,检查索引是否在有效范围内。
csharpint index = GetUserInput(); if (index >= 0 && index < scores.Length) { Console.WriteLine(scores[index]); } else { Console.WriteLine("索引无效!"); }
-
3. FormatException:格式异常
-
当一个方法的参数格式不符合预期时抛出,最常见于字符串向其他数据类型(如数字、日期)的转换。
csharpstring userInput = "twelve"; int number = int.Parse(userInput); // "twelve" 不是有效的整数格式 string dateString = "2023-30-01"; // 无效的日期 (30月) DateTime date = DateTime.Parse(dateString); // -
解决方案::
-
使用
TryParse模式 :这是应对FormatException的最佳实践 。TryParse方法会尝试转换,如果成功,返回true并通过out参数提供结果;如果失败,返回false而不会抛出异常 。这遵循了"先看后跳"(LBYL)的原则。csharpstring userInput = Console.ReadLine(); if (int.TryParse(userInput, out int number)) { Console.WriteLine($"转换成功: {number}"); } else { Console.WriteLine("输入无效,请输入一个数字。"); }
-
3.1 ArgumentException 家族 (ArgumentNullException, ArgumentOutOfRangeException)
-
ArgumentException:通用的参数错误,表示传递给方法的某个参数不合法。 -
ArgumentNullException:ArgumentException的子类,特指一个不应为null的参数被传入了null。 -
ArgumentOutOfRangeException:ArgumentException的子类,特指一个参数的值超出了可接受的范围。 -
示例::
csharppublic void SetUserName(string name) { // 卫语句:主动防御,而不是等着用到 name 时再爆炸 if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("用户名不能为空或仅包含空白字符。", nameof(name)); } this.UserName = name; } public void SetDiscount(double percentage) { if (percentage < 0 || percentage > 1) { throw new ArgumentOutOfRangeException(nameof(percentage), "折扣必须在0和1之间。"); } this.Discount = percentage; } // 调用者犯错 myObject.SetUserName(null); // 会立即捕获到 ArgumentException,而不是后面的 NullReferenceException
4. InvalidCastException:无效转换异常
-
在运行时执行了一个显式的类型转换,但源类型无法被转换为目标类型时发生
-
示例::
csharpobject myObject = "Hello World"; // 这是一个字符串,不能被强制转换为一个 StringBuilder StringBuilder sb = (StringBuilder)myObject; -
解决方案::
-
使用
as运算符 :as运算符尝试进行转换,如果成功,返回转换后的对象;如果失败,它会返回null而不是抛出异常 。然后你可以配合null检查来安全地执行后续操作。csharpStringBuilder sb = myObject as StringBuilder; if (sb != null) { sb.Append("..."); } else { Console.WriteLine("对象不是 StringBuilder 类型。"); } -
使用
is运算符和模式匹配 :is运算符检查一个对象是否兼容某个类型csharpif (myObject is StringBuilder sb) { sb.Append("..."); }
as/isvs. 强制转换 :与TryParsevs.Parse类似,如果你不确定转换能否成功,就应该使用as或is。只有在你100%确定类型兼容时,才使用强制转换,因为它能更早地暴露逻辑错误。 -
5. InvalidOperationException:无效操作异常
-
当方法调用对于对象的当前状态无效时抛出。错误不在于参数,而在于对象"还没准备好"或"已处于不当状态"。
-
示例::
csharp// 场景1: 修改正在迭代的集合 var numbers = new List<int> { 1, 2, 3, 4 }; foreach (var number in numbers) { if (number == 2) { numbers.Remove(number); // 不能在 foreach 循环中修改集合 } } // 场景2: 使用已耗尽的迭代器 var enumerator = numbers.GetEnumerator(); while(enumerator.MoveNext()) { } // 迭代器已到末尾 Console.WriteLine(enumerator.Current); // BOOM! (或返回默认值,取决于实现)
6. IOException 家族 (FileNotFoundException, DirectoryNotFoundException, etc.)
-
所有与输入/输出(I/O)操作相关的错误的基类。
FileNotFoundException:尝试访问一个不存在的文件。DirectoryNotFoundException:文件路径中的某个目录不存在。UnauthorizedAccessException:程序没有足够的权限去访问文件或目录。
-
核心特点 :这类异常是典型的必须使用
try-catch来处理的场景。因为即使你在操作前用File.Exists检查,也无法保证在检查和实际操作之间的瞬间,文件不会被用户或其他程序删除(这被称为"竞态条件")。 -
最佳实践 :
csharptry { string content = File.ReadAllText(@"C:\secret\data.txt"); // ... process content ... } catch (FileNotFoundException ex) { Console.WriteLine("错误:文件未找到。请确认文件路径是否正确。"); } catch (UnauthorizedAccessException ex) { Console.WriteLine("错误:权限不足。请尝试以管理员身份运行程序。"); } catch (IOException ex) // 兜底处理其他I/O错误 { Console.WriteLine($"发生了一个I/O错误: {ex.Message}"); }
结语
点个赞,关注我获取更多实用 C# 技术干货!如果觉得有用,记得收藏本文