CodeGym /课程 /C# SELF /与文件操作相关的常见异常

与文件操作相关的常见异常

C# SELF
第 38 级, 课程 0
可用

1. 介绍:为啥文件总爱“作妖”?

有时候打开文档——系统却提示:"未找到文件"。或者尝试保存时弹出:"拒绝访问"。这就是文件开始“作妖”的典型场景。跟我们遇到乱码是编码问题不同,这里的问题通常和文件系统本身有关。

可以把文件系统想象成一个大图书馆,应用程序就是图书管理员。当它去请求一本书的时候,可能会得到不同的回复:

  • 书(也就是文件)不存在——根本找不到。
  • 图书馆里放书的区根本没了——所需目录不存在。
  • 书被锁着或借出去了——文件被别的进程占用或没有访问权限。
  • 书架塞满了——磁盘空间不足,无法创建新文件。
  • 或者你把书放到了 DVD 归还处——也就是执行了不被支持的操作。

在 C# 里这些情况会通过异常来体现。开发者的任务不是坐视程序“崩溃”,而是预判可能的问题并优雅地处理它们。毕竟没人想看到一大堆莫名其妙的错误文本。

我们已经熟悉 try-catch 结构。它是我们的救生圈,可以“捕获”异常并采取措施,而不是让程序直接崩掉。

// 这是我们熟悉的老朋友,来自第57讲的提醒
try
{
    // 这里写可能会抛错的代码
    // 比如,尝试读取文件
}
catch (Exception ex) // 捕获任意异常
{
    // 在这里处理错误
    Console.WriteLine($"哎呀,发生错误了: {ex.Message}");
}

今天我们要深入一些和文件操作相关的特定异常。这样能让我们的代码更健壮,即便文件系统在“闹情绪”,程序也能和它有礼貌地打交道。

异常不是 bug,是求救信号!

要明白:异常不一定是你代码写错了。很多时候这是外部环境(比如文件系统)向你发出的信号。文件系统就是典型的外部环境。你可能写了完全正确的读取文件逻辑,但如果用户在程序读取之前把文件删了,你会收到异常。这是正常的!作为开发者,你的任务是让程序知道如何应对这种情况。

我们来看看在处理文件时最常见的“求救信号”。

2. FileNotFoundException:文件根本就没

这是处理文件时最常见的异常。当你尝试打开、读取或对一个在给定路径上不存在的文件做操作时,会抛出它。

生活中的例子:你让朋友去拿本书"C# 14 编程入门",他却说:"我没有这本书"。同理,你的程序向操作系统请求:"给我 settings.txt",系统回复:"抱歉,没有这个文件"。

举个例子,程序尝试读取文件 abracadabra.txt。如果没有该文件,我们应该告诉用户,而不是直接崩溃。

try
{
    using var reader = new StreamReader("abracadabra.txt");
    Console.WriteLine(reader.ReadToEnd());
}
catch (FileNotFoundException ex)
{
    Console.WriteLine("未找到文件: " + ex.FileName);
}

生活化一点:就像你在车站等公交,但公交根本没来——很心塞。

提示:这个异常常伴随错误的路径(比如你忘了程序的工作目录不是你想的那个)。

3. DirectoryNotFoundException:文件夹去哪儿了

这个异常跟 FileNotFoundException 很像,但它关心的是目录而不是文件本身。如果你指向的路径中间的某个文件夹不存在,就会抛出这个异常。例如路径 "C:\MyDocuments\MyProject\Data\report.txt",但 Data 文件夹不存在,你会得到 DirectoryNotFoundException。

在我们的应用里,如果想把设置存到子文件夹 data,路径像 "./data/app_settings.txt",但文件夹不存在,读写时就会出问题。

DirectoryNotFoundException 可以单独捕获,也可能作为更通用的 IOException 的一部分出现。

try
{
    using var writer = new StreamWriter(@"C:\very\strange\path\file.txt");
    writer.WriteLine("Hello world");
}
catch (DirectoryNotFoundException ex)
{
    Console.WriteLine("未找到目录!");
}

常见错误:文件夹随时可能被删掉(比如有人清理临时文件),或者写入路径配置有误。

4. UnauthorizedAccessException:禁止入内!

想象你想把书放到某个书架,但上面挂着牌子"仅限员工"。这就是 UnauthorizedAccessException!当程序没有访问文件或目录的权限时会抛出。常见原因包括:

  • 用户权限不足:例如尝试写入只有管理员才能写的目录(比如 C:\Windows)。
  • 文件被标记为只读:尝试修改一个带有只读属性的文件。
  • 文件是系统或隐藏文件:对这些文件可能有特殊限制。

在企业环境或把程序装到受保护目录时,这种问题非常常见。

示例:尝试往系统目录写文件(小心运行,别乱塞系统目录)。

try
{
    using var writer = new StreamWriter("/system/settings.conf");
    writer.WriteLine("一切权力属于学生!");
}
catch (UnauthorizedAccessException ex)
{
    Console.WriteLine("没有文件或目录的访问权限!");
}

如果在没有管理员权限下运行,上面的代码很可能提示 "对该文件夹的访问被拒绝"。用管理员运行则可能成功。这个例子说明处理 UnauthorizedAccessException 能让用户明白为什么操作失败。

5. IOException:文件系统的万能“哎呦”

IOException 是与 I/O 操作相关的通用异常。当设备或文件系统出现问题,但不属于更具体的异常(如 FileNotFoundException 或 UnauthorizedAccessException)时,会抛出它。

典型会触发 IOException 的场景:

  • 文件被另一个程序占用:比如你想删除一个在记事本或另一个程序中打开的文件。
  • 磁盘空间已满:没有足够空间写入文件。
  • 文件或文件系统损坏:虽然少见,但会发生。
  • 网络问题:如果文件在网络盘上,连接断开会出问题。
  • 文件或目录名过长:(这有时是 PathTooLongException,但有时也会落到 IOException)。

IOException 有点像一个“万能钥匙”,覆盖很多种问题。捕获它时,最好查看它的 Message 属性获取更具体的错误信息。

try
{
    using var file = new FileStream("busyfile.txt", FileMode.Open, FileAccess.ReadWrite, FileShare.None);
    // 以某种方式保持文件打开
    // 同时在其他地方:
    using var writer = new StreamWriter("busyfile.txt");
    writer.WriteLine("尝试写入...");
}
catch (IOException ex)
{
    Console.WriteLine("输入/输出 错误: " + ex.Message);
}

重要: IOException 是许多文件相关异常的基类。

6. 其他但同样重要的问题

PathTooLongException

这个问题不太常见,但会碰到:路径(或文件/目录名)对操作系统来说太长。例如你把《战争与和平》的内容塞进文件名,Windows 可不会开心。

在 Windows 上历史限制是完整路径 260 个字符。新版本的 OS 和 .NET 支持“长路径”,但默认情况下不一定可用。

try
{
    string veryLongPath = new string('a', 300); // 300 个字符!
    using var writer = new StreamWriter(veryLongPath + ".txt");
    writer.WriteLine("这是个太长的文件名!");
}
catch (PathTooLongException ex)
{
    Console.WriteLine("文件名或路径太长!");
}

NotSupportedException

这是个少见但“超现实”的情况:比如你传给 StreamReader 或 FileStream 构造函数的路径字符串格式不对,含有禁止字符,或者用了奇怪的路径像 C:::\wow???\file.txt。

7. 有用的细节

哪个异常对应什么原因

异常 触发原因 示例情形
FileNotFoundException
未找到文件 打开不存在的文件
DirectoryNotFoundException
未找到目录 在已被删除的文件夹中打开文件
UnauthorizedAccessException
没有访问权限 写入受保护目录
IOException
通用 I/O 错误 文件被其他进程占用
PathTooLongException
路径或文件名过长 文件/文件夹名太长
NotSupportedException
路径格式不支持 路径包含禁止字符

常见错误与特殊情况

示例:文件被另一个进程占用

想象你像个真正的黑客一样在 Notepad 打开了一个文本文件却忘了关。此时程序尝试写入同一个文件,就会碰到 IOException(或者出现“sharing violation”)。

示例:没有权限

尝试在没有管理员权限的情况下保存到 C:\Windows 会得到 UnauthorizedAccessException。同样,如果你以只读方式打开文件却尝试写入,也会遇到这个问题。

示例:路径不合法

在 Windows 下文件名不能包含字符 <>:"/\|?*。如果尝试用这些字符命名文件,程序会抛出 NotSupportedException(或 ArgumentException)。

示例:磁盘空间不足

这通常也会抛出 IOException——比如磁盘满了(所以别忘了偶尔清理一下 Downloads 文件夹)。

别踩坑:错误检测的最佳实践

  • 在尝试打开文件前使用 File.Exists 和 Directory.Exists 检查文件/目录是否存在。但注意:文件可能在检查后立即被删除或创建(经典的 race condition)。
  • 别把异常吞了(不要只写 catch { }),除非你是在写严格的错误记录器。至少要记录日志或告诉用户发生了什么。
  • 尽量捕获具体异常(FileNotFoundException, DirectoryNotFoundException),不要只捕获泛型的 Exception。
  • 写跨平台应用时要考虑不同平台的权限、路径格式、文件名长度等差异。
  • 处理文本文件时总是明确指定编码,否则容易出惊喜。
  • 对大量文件操作,尽量用批量处理并在每一步处理失败时考虑回退或重试。

典型异常速查表

异常 什么时候会发生? 如何防止/处理?
FileNotFoundException
指定路径上没有文件 用 File.Exists 检查或创建文件
DirectoryNotFoundException
路径中包含不存在的目录 检查路径,使用 Directory.CreateDirectory 创建目录
UnauthorizedAccessException
没有文件/文件夹的权限,文件为只读,或被其它进程占用 以合适的用户运行,检查 ACL,正确关闭文件句柄
IOException
通用 I/O 错误,文件被占用,磁盘空间不足 用 try-catch,尽量不要长时间占用文件
PathTooLongException
路径或文件名太长 缩短路径,使用相对路径
NotSupportedException
路径格式不正确 检查路径字符串是否包含禁止字符

发生文件错误时的流程图

flowchart TD
    A[文件操作] --> B{抛出异常了吗?}
    B -- 否 --> C[操作成功完成]
    B -- 是 --> D{是哪种异常?}
    D -- FileNotFound --> E[让用户指定正确文件或创建它]
    D -- DirectoryNotFound --> F[创建缺失的目录]
    D -- UnauthorizedAccess --> G[让用户以合适权限重启程序]
    D -- IOException --> H[检查是谁占用文件,检查磁盘]
    D -- PathTooLong --> I[缩短路径]
    D -- NotSupported --> J[检查路径格式]
    D -- Other --> K[显示信息并复查日志]

8. 在真实应用中的典型写法

扩展我们的教学项目,假设有个小程序把用户笔记保存到文件,然后再读取它们。

string notesPath = "notes.txt";

Console.Write("请输入笔记: ");
string note = Console.ReadLine();

try
{
    // 保存笔记
    using var writer = new StreamWriter(notesPath, true, Encoding.UTF8);
    writer.WriteLine(note);

    // 读取所有笔记
    Console.WriteLine("你的笔记:");
    using var reader = new StreamReader(notesPath, Encoding.UTF8);
    Console.WriteLine(reader.ReadToEnd());
}
catch (FileNotFoundException)
{
    Console.WriteLine("未找到笔记文件。尝试手动创建它。");
}
catch (DirectoryNotFoundException)
{
    Console.WriteLine("笔记文件的路径不正确。检查文件夹是否存在。");
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("没有读写文件的权限。请以管理员身份运行程序。");
}
catch (IOException ex)
{
    Console.WriteLine("发生输入/输出错误: " + ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("意外错误: " + ex.Message);
}

这个例子展示了常见场景:尝试保存数据到文件,然后再读回。每个 catch 块处理一类错误——从文件或目录不存在,到权限问题,再到通用的 I/O 错误。这样程序不会在第一次失败时崩溃,而是告诉用户发生了什么以及该怎么做。这个方法能让程序更稳、更友好。

2
任务
C# SELF, 第 38 级, 课程 0
已锁定
处理 FileNotFoundException 异常
处理 FileNotFoundException 异常
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION