HOWTO · Csharp

在 C# 中检查文件是否存在

C# 的 System.IO 命名空间提供了 File.Exists 方法,可以检查指定路径上的文件是否存在。

本页内容

在程序读取、写入或删除文件之前,通常最好先确认该文件是否真的存在。C# 通过 File.Exists 提供这种检查——它是 System.IO 命名空间中的一个方法,用于测试一个路径并报告布尔答案。本指南将展示如何调用它、相对路径和绝对路径如何决定所检查的内容、false 结果究竟告诉你什么,以及如何在不盲目信任答案的情况下根据它采取行动。

使用 File.Exists 检查文件是否存在

File.Exists 是一个静态方法,因此你要在 File 类本身上调用它,而不是在某个实例上调用。你把一个路径作为字符串传给它,当操作系统能够确认该路径处存在一个常规文件时它返回 true,无法确认时返回 false。

运行这个示例:

本节中的代码以及显示的标签或输出仍为英文。

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string path = Path.Combine(Path.GetTempPath(), "delftstack-demo.txt");
        File.WriteAllText(path, "demo content");
        bool fileExists = File.Exists(path);
        Console.WriteLine($"Does the file exist? {fileExists}");
        File.Delete(path);
    }
}

输出:

Does the file exist? True

该程序在系统临时目录中创建一个小文件,用 File.Exists 检查它,并打印答案——结果是 True,因为文件刚刚被创建。把 path 指向你需要测试的任何位置;调用方式保持不变,结果始终是一个普通的 bool,可以直接用于 if 语句,也可以存入变量备用。

选择要检查的路径

你传入的字符串决定了检查哪个位置,因此要有意识地选择它。

绝对路径完整地标明文件——Windows 上是 C:\Data\report.txt,Linux 上是 /var/data/report.txt。无论你的程序从哪里运行,它都指向同一个位置,所以当位置固定、或来自配置或用户输入时,应优先使用它。

像 report.txt 或 logs/report.txt 这样的相对路径是相对于进程工作目录解析的,也就是 Environment.CurrentDirectory 的值。它通常是进程启动时所在的文件夹——不一定是包含你的可执行文件的文件夹——而且其他代码可以更改它,因此相对路径可能会悄悄指向你预期之外的位置。

运行下面的示例,看看同一个相对名称如何解析到两个不同的位置:

本节中的代码以及显示的标签或输出仍为英文。

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string dir = Path.Combine(Path.GetTempPath(), "delftstack-cwd-demo");
        Directory.CreateDirectory(dir);
        File.WriteAllText(Path.Combine(dir, "delftstack-report-7f3a1c.txt"), "demo");

        Console.WriteLine(File.Exists("delftstack-report-7f3a1c.txt"));

        Directory.SetCurrentDirectory(dir);
        Console.WriteLine(File.Exists("delftstack-report-7f3a1c.txt"));

        Directory.SetCurrentDirectory(Path.GetTempPath());
        File.Delete(Path.Combine(dir, "delftstack-report-7f3a1c.txt"));
        Directory.Delete(dir);
    }
}

输出:

False
True

第一次检查在原始工作目录中查找该文件,结果什么也没找到。Directory.SetCurrentDirectory 把进程切换到存放该文件的文件夹之后,同一个相对名称就解析到了它,检查返回 True。

在 C# 源代码中书写 Windows 路径时,反斜杠之所以需要注意,仅仅是因为字符串语法,而不是因为 Windows 要求双份分隔符。在普通字符串中,\ 会开始一个转义序列,所以要写成 "C:\\Data\\report.txt";在以 @ 为前缀的逐字字符串中,写单个反斜杠即可:@"C:\Data\report.txt"。.NET 在 Windows 上也接受正斜杠("C:/Data/report.txt"),而在 Linux 和 macOS 上 / 是常规分隔符,因此 Path.Combine 是构建路径最具可移植性的方式。

解读结果及其局限

File.Exists 只回答一个狭窄的问题:操作系统此刻能否确认该路径处存在一个文件?应把 false 理解为“无法确认”,而不是专门指“文件不存在”。当路径为 null 或格式错误、路径指向的是目录而不是文件(检查文件夹请使用 Directory.Exists)、或检查本身遇到麻烦(例如缺少遍历这些文件夹的权限)时,该方法同样返回 false。

这些问题会被折叠进 false 结果中,而不是被抛出,因此在 File.Exists 外面包一层 try/catch 几乎永远观察不到任何东西。如果你的程序需要知道路径失败的原因——以便记录日志、警告用户或决定是否重试——请自己检查具体条件,或者直接尝试真正的文件操作并读取它报告的错误。

该调用是同步的,会访问文件系统。通常这很快,但在网络共享或较慢的磁盘上可能花费明显的时间,因此当一次保存的结果就够用时,应避免在紧凑的循环中反复调用它。

安全地使用结果

把答案当作一个快照,而不是一种保证。在你调用 File.Exists 和你真正打开文件之间,另一个进程——甚至你自己程序中的另一个线程——可能会删除、移动或替换该文件,因此即使是 if (File.Exists(path)) File.ReadAllText(path) 也仍然可能失败。

稳妥的习惯是直接对文件执行操作,并处理操作本身的结果:

本节中的代码以及显示的标签或输出仍为英文。

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string path = Path.Combine(Path.GetTempPath(), "delftstack-read-demo.txt");
        File.WriteAllText(path, "hello from disk");

        if (File.Exists(path))
        {
            try
            {
                Console.WriteLine(File.ReadAllText(path));
            }
            catch (IOException)
            {
                Console.WriteLine("The file could not be read.");
            }
            catch (UnauthorizedAccessException)
            {
                Console.WriteLine("Access to the file was denied.");
            }
        }

        File.Delete(path);
    }
}

输出:

hello from disk

这里,当文件不存在时 File.Exists 干净地跳过了这项工作,而 File.ReadAllText 外面的 try/catch 则覆盖了检查无法覆盖的情况——已消失的文件(表现为 FileNotFoundException,它是 IOException 的一种)、被另一个进程锁定的文件,或被拒绝的访问。当你的唯一目标只是读取文件时,可以去掉这个检查,让 catch 来处理文件不存在的情况,这样就彻底消除了竞态。把 File.Exists 留给那些布尔答案本身就足够用的决策:显示一条消息、在创建文件和打开文件之间做选择,或者跳过一个清理步骤。