-
异常处理——用C#文件读写错误捕获与恢复
第一部分:C#基础入门
实例7:异常处理——文件读写错误捕获与恢复
实例介绍
文件读写是开发中常见场景,但磁盘满、权限不足、文件不存在等问题随时会导致程序崩溃。异常处理的核心是让程序在错误发生时优雅恢复,而非直接崩溃。本实例通过实现一个健壮的文件读写工具,掌握try-catch-finally、using语句、自定义异常、异常链等核心技巧,理解“捕获-处理-释放资源”的完整流程,让你的程序在复杂环境下更稳定。
需求分析
1.文件读取:读取指定路径的文本文件,处理文件不存在、权限不足等错误;
2.文件写入:写入内容到指定文件,处理vb.net教程C#教程python教程SQL教程access 2010教程磁盘满、被占用等错误;
3.错误恢复:文件不存在时自动创建,内容为空时提示用户;
4.资源安全:确保文件流无论是否发生异常都能正确关闭;
5.自定义异常:处理文件内容不符合预期的场景(如格式错误);
6.友好提示:给用户清晰的错误原因,而非技术栈跟踪信息。
代码实现
csharp
using System;
using System.IO;
using System.Text;
namespace ExceptionHandlingDemo
{
// 自定义异常:文件内容不符合预期
public class InvalidFileContentException : Exception
{
public InvalidFileContentException(string message) : base(message) { }
public InvalidFileContentException(string message, Exception inner) : base(message, inner) { }
}
class FileHandler
{
static void Main(string[] args)
{
Console.Write("请输入文件路径(如:test.txt):");
string filePath = Console.ReadLine()?.Trim() ?? "";
if (string.IsNullOrEmpty(filePath))
{
Console.WriteLine("文件路径不能为空!");
Console.ReadKey();
return;
}
try
{
// 读取文件
string content = ReadFile(filePath);
Console.WriteLine($"文件内容:
{content}");
// 追加内容
Console.Write("请输入要追加的内容:");
string appendContent = Console.ReadLine() ?? "";
WriteFile(filePath, appendContent, append: true);
Console.WriteLine("内容追加成功!");
}
catch (InvalidFileContentException ex)
{
Console.WriteLine($"内容错误:{ex.Message}");
}
catch (IOException ex)
{
Console.WriteLine($"IO错误:{ex.Message}");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine($"权限错误:{ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"未知错误:{ex.Message}");
}
finally
{
Console.WriteLine("
操作结束(finally块执行,确保资源释放)");
}
Console.ReadKey();
}
/// <summary>
/// 读取文件内容
/// </summary>
public static string ReadFile(string filePath)
{
if (!File.Exists(filePath))
{
// 文件不存在:提示用户并创建空文件
Console.WriteLine($"文件 {filePath} 不存在,是否创建?(Y/N)");
if (Console.ReadKey().Key == ConsoleKey.Y)
{
WriteFile(filePath, "", append: false);
Console.WriteLine($"
文件 {filePath} 已创建");
return "";
}
throw new FileNotFoundException($"文件 {filePath} 不存在", filePath);
}
// using语句:自动释放StreamReader资源(即使发生异常)
using StreamReader reader = new StreamReader(filePath, Encoding.UTF8);
string content = reader.ReadToEnd();
// 自定义异常:内容为空时抛出
if (string.IsNullOrWhiteSpace(content))
{
throw new InvalidFileContentException($"文件 {filePath} 内容为空");
}
return content;
}
/// <summary>
/// 写入文件内容
/// </summary>
public static void WriteFile(string filePath, string content, bool append = false)
{
try
{
// using语句:自动关闭FileStream
using FileStream fs = new FileStream(filePath, append ? FileMode.Append : FileMode.Create, FileAccess.Write);
using StreamWriter writer = new StreamWriter(fs, Encoding.UTF8);
writer.Write(content);
}
catch (IOException ex)
{
// 异常链:包装原始异常,保留上下文
throw new IOException($"写入文件 {filePath} 失败", ex);
}
}
}
}
逐行讲解
-
自定义异常定义
csharp
public class InvalidFileContentException : Exception
{
public InvalidFileContentException(string message) : base(message) { }
public InvalidFileContentException(string message, Exception inner) : base(message, inner) { }
}
作用:处理业务相关的错误(如文件内容为空),让异常更具语义;
继承Exception:所有自定义异常都应继承自Exception(而非ApplicationException,后者已过时);
InnerException:保留原始异常信息,方便调试(如包装IO异常)。
2. 读取文件的异常处理
csharp
public static string ReadFile(string filePath)
{
if (!File.Exists(filePath))
{
// 用户确认后创建文件
if (Console.ReadKey().Key == ConsoleKey.Y) WriteFile(filePath, "", false);
else throw new FileNotFoundException(...);
}
using StreamReader reader = new StreamReader(filePath);
string content = reader.ReadToEnd();
if (string.IsNullOrWhiteSpace(content)) throw new InvalidFileContentException(...);
return content;
}
File.Exists检查:提前判断文件是否存在,提供恢复选项(创建文件);
using语句:自动调用reader.Dispose(),确保文件流关闭(即使抛出异常);
自定义异常抛出:当内容为空时,抛出业务异常,让调用者处理。
3. 写入文件的异常处理
csharp
public static void WriteFile(string filePath, string content, bool append)
{
try
{
using FileStream fs = new FileStream(filePath, append ? FileMode.Append : FileMode.Create, FileAccess.Write);
using StreamWriter writer = new StreamWriter(fs);
writer.Write(content);
}
catch (IOException ex)
{
throw new IOException($"写入失败", ex); // 异常链
}
}
FileStream参数:FileMode.Append追加内容,FileMode.Create覆盖内容;
异常包装:将原始IO异常包装为更友好的信息,同时保留InnerException(原始异常);
嵌套using:StreamWriter依赖FileStream,嵌套using确保两者都被释放。
4. Main方法中的异常捕获
csharp
try
{
string content = ReadFile(filePath);
WriteFile(filePath, appendContent);
}
catch (InvalidFileContentException ex) { ... }
catch (IOException ex) { ... }
catch (Exception ex) { ... }
finally { ... }
捕获顺序:具体异常优先于通用异常(如先捕获自定义异常,再捕获IO异常,最后捕获Exception);
finally块:无论是否发生异常都会执行(这里用于输出操作结束信息);
友好提示:给用户非技术术语的错误信息(如“权限错误”而非“UnauthorizedAccessException”)。
基础知识拓展
- 异常的分类
| 类型 | 说明 | 示例 |
|---|---|---|
| 系统异常 | .NET框架定义的异常,继承自SystemException | FileNotFoundException、IOException |
| 应用异常 | 自定义异常,继承自Exception | InvalidFileContentException |
| 运行时异常 | 程序运行时发生的错误 | NullReferenceException、IndexOutOfRangeException |
-
try-catch-finally的执行顺序
1.try块:执行正常逻辑,若发生异常则跳转到对应catch块;
2.catch块:匹配异常类型,执行处理逻辑;
3.finally块:无论是否发生异常,都会执行(用于释放资源);
注意:若try块中有return,finally块仍会在return前执行。 -
using语句的本质
using (var resource = new DisposableResource()) { ... }
等价于:
csharp
var resource = new DisposableResource();
try
{
...
}
finally
{
resource.Dispose(); // 即使发生异常也会执行
}
适用场景:所有实现IDisposable接口的对象(如文件流、数据库连接);
优势:简化资源管理,避免遗漏Dispose调用。
4. 异常处理最佳实践
1.不要吞异常:避免catch (Exception) { }(不处理异常且不抛出);
2.具体异常优先:先捕获具体异常(如FileNotFoundException),再捕获通用异常;
3.提供有用信息:异常消息应说明“什么错、哪里错、怎么解决”;
4.保留异常链:用InnerException传递原始异常信息;
5.释放资源:用using或finally确保资源释放(如文件流、网络连接)。
扩展思考
1.日志记录:用Serilog或log4net记录异常到文件/数据库,方便排查问题;
2.重试机制:文件被占用时,重试3次后再抛出异常(用Task.Delay实现);
3.异步文件操作:用async/await处理异步IO,异常捕获方式不变(try-catch包裹await调用);
4.跨平台路径:用Path.Combine处理路径(如Path.Combine(Environment.CurrentDirectory, "test.txt"));
5.验证文件格式:读取CSV文件时,验证每行的列数,抛出InvalidFileFormatException。
总结
异常处理的核心是让程序更健壮,而非掩盖错误。正确的异常处理应做到:
1.捕获并处理:对能恢复的错误(如文件不存在)提供解决方案;
2.传递并记录:对无法处理的错误(如权限不足),传递给上层并记录日志;
3.释放资源:确保无论是否发生异常,资源都能被正确释放;
4.友好提示:给用户清晰的错误信息,帮助用户解决问题。
通过本实例,你已掌握文件读写中的异常处理技巧,这些技巧可迁移到数据库操作、网络请求等场景,让你的程序在复杂环境下稳定运行。
本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49398.html










