当前位置: 首页 > news >正文

文件和文件夹不存在的时候,FileSystemWatcher 监听不到文件的改变?如果递归地监听就可以了

当你需要监视文件或文件夹的改变的时候,使用 FileSystemWatcher 便可以完成。不过,FileSystemWatcher 对文件夹的监视要求文件夹必须存在,否则会产生错误“无效路径”。

那么,如果文件或文件夹不存在的时候可以怎么监视文件的改变呢?更麻烦的是如果顶层很多级文件夹都不存在,怎么能监视呢?本文将告诉你方法。

本文的代码适用于 .NET Framework 和 .NET Core,同时不需要任何第三方依赖。


本文内容

      • 方法一:创建文件夹(在逃避问题,但也不失为一种解决思路)
      • 方法二:递归监视文件夹
        • 代码实现
        • 完整的代码和使用方法
        • 此方法的特点,优势和不足
        • 附所有源码
        • 参考资料

方法一:创建文件夹(在逃避问题,但也不失为一种解决思路)

如果文件夹不存在,把它创建出来就可以监视了嘛!这其实是在逃避问题。不过我把它写出来是因为如果我不说,可能有些小伙伴原本简单的问题就会变得复杂化。

public void Watch(string file)
{
    path = Path.GetFullPath(file);
    var directory = Path.GetDirectoryName(path);
    var file = Path.GetFileName(path);

    // 如果文件夹不存在,则创建文件夹。
    if (!Directory.Exists(directory))
    {
        Directory.CreateDirectory(directory);
    }

    // 监视 directory 文件夹下的 file 文件的改变
    var watcher = new FileSystemWatcher(directory, file)
    {
        EnableRaisingEvents = true,
        NotifyFilter = NotifyFilters.LastWrite,
    };
    watcher.Changed += FinalFile_Changed;
    // 使用 watcher 做其他的事情。
}

private void FinalFile_Changed(object sender, FileSystemEventArgs e)
{
    // 当文件改变的时候,这里的代码会执行。
}

以上代码的含义是:

  1. 将文件路径取出来,分为文件夹部分和文件部分;
  2. 判断文件夹是否存在,如果不存在,则创建文件夹;
  3. 监视文件夹中此文件的改变。

需要说明的是,FileSystemWatcher 原本是监视文件夹的,第一个参数是监视的文件夹的路径,而第二个参数是监视文件或文件夹的过滤通配符。

不过,官方文档的说明是:

To watch a specific file, set the Filter property to the file name. For example, to watch for changes in the file MyDoc.txt, set the Filter property to “MyDoc.txt”.

如果你需要监听一个特定的文件,那么直接将后面的过滤器设定为文件名,那么就会直接监视到对应的文件。

如果你的业务当中,反正始终都是要创建这个文件的,那么一开始创建了这个文件夹就能避免不少的麻烦。这也是我把这个方法放到这里作为首选方法的原因。虽然实际上这是在逃避问题,但真的是一个好方法。

方法二:递归监视文件夹

这种方法适用于如果文件或者文件夹不存在时,你不能创建这个文件夹的情况。也许是你的业务需要,也许因为你正在写库,库作为最为通用的业务,不希望改变用户的环境。

这时,我们可以考虑的思路是 —— 递归地监视文件或文件夹

例如,我们有这样的文件夹结构:

C:\a\b\x.txt

希望监听 x.txt 的改变。

那么,如果 b 文件夹不存在,就监听 a 文件夹,如果 a 文件夹也不存在,那么就监听 C: 驱动器。实际上,我们不需要再去考虑 C: 驱动器也不存在的情况了(当你真的遇到的时候,考虑业务上规避吧……)。

代码实现

既然需要递归监视,那么我们需要查找第一次监视的时候,需要到哪一层。

这里,我们可以用一个 while 循环来进行,一层一层查找文件夹。直到能够找到一层,文件夹存在而子文件夹不存在的情况。这时我们便能够监视子文件夹的创建了。

我写了一个函数,用于返回这时存在的那个文件夹,和不存在的那个子文件夹或者文件。

当然有特殊情况,就是文件直接就已经存在的情况下,也是返回文件所在的文件夹和此文件名的。

private (string directory, string file) FindWatchableLevel()
{
    var path = _file.FullName;

    // 如果文件存在,就返回文件所在的文件夹和文件本身。
    if (File.Exists(path))
    {
        return (Path.GetDirectoryName(path), Path.GetFileName(path));
    }

    // 如果文件不存在,但文件夹存在,也是返回文件夹和文件本身。
    // 这一点在下面的第一层循环中体现。

    // 对于每一层循环。
    while (true)
    {
        var directory = Path.GetDirectoryName(path);
        var file = Path.GetFileName(path);

        // 检查文件夹是否存在,只要文件夹存在,那么就可以返回。
        if (Directory.Exists(directory))
        {
            return (directory, file);
        }

        // 如果连文件夹都不存在,那么就需要查找上一层文件夹。
        path = directory;
    }
}

接下来,根据得到的文件夹和文件,判断其存在与否,决定是监视这个文件的改变,还是监视文件/文件夹结构的改变。如果文件/文件夹的结构改变,那么就需要重新调用这个方法再查找应该监视的文件夹了。

public void Watch()
{
    var (directory, file) = FindWatchableLevel();
    if (File.Exists(_file.FullName))
    {
        // 如果文件存在,说明这是最终的文件。
        // 注意使用 File.Exists 判断已存在的同名文件夹时会返回 false。
        _watcher = new FileSystemWatcher(directory, file)
        {
            EnableRaisingEvents = true,
            NotifyFilter = NotifyFilters.LastWrite,
        };
        _watcher.Changed += FinalFile_Changed;
        _watcher.Deleted += FileOrDirectory_CreatedOrDeleted;
    }
    else
    {
        // 注意这里的 file 可能是文件也可能是文件夹。
        _watcher = new FileSystemWatcher(directory, file)
        {
            EnableRaisingEvents = true,
        };
        _watcher.Created += FileOrDirectory_CreatedOrDeleted;
        _watcher.Renamed += FileOrDirectory_CreatedOrDeleted;
        _watcher.Deleted += FileOrDirectory_CreatedOrDeleted;
    }
}

我们通过 File.Exists(_file.FullName) 来判断最终的文件是否存在,用以区分是在监视最终的文件改变,还是监视文件夹结构的改变。

private void FileOrDirectory_CreatedOrDeleted(object sender, FileSystemEventArgs e)
{
    // 在文件/文件夹结构发生改变的时候,重新监视。
    _watcher?.Dispose();
    Watch();
}

private void FinalFile_Changed(object sender, FileSystemEventArgs e)
{
    // 这里就是最终文件改变的地方了。
}

完整的代码和使用方法

由于代码还是有一点点多。如果放到你原有的业务当中,对你的业务代码确实是一种污染。所以我封装了一个类 FileWatcher。它不需要依赖任何就可以使用,你可以将它拷贝到你的项目当中。

代码可以阅读本文文末,或者前往 gist 查看:FileWatcher that helps you to watch a single file change even if the file or it’s owner folders does not exists.。

使用方法与 FileSystemWatcher 类似,但是更简单:

_watcher = new FileWatcher(@"C:\Users\walterlv\Desktop\demo.txt");
_watcher.Changed += OnFileChanged;
_watcher.Watch();
private void OnFileChanged(object sender, EventArgs e)
{
    // 最纯粹的文件改变事件,仅在文件的内容真的改变的时候触发。
}

此方法的特点,优势和不足

实际上,FileSystemWatcher 的监视也是有一些空洞的。如果你只是监视一级文件夹而不是递归监视子文件夹(通过设置 IncludeSubdirectories 属性来指定),那么就会存在一些情况是监视不到的。然而如果你真的递归监视子文件夹,又会监听到大量的事件需要过滤。

那么此方法可以支持和不支持的情况有哪些呢?

依然假设监视的文件是:C:\a\b\x.txt

支持这些情况:

  1. 一开始文件 x.txt 不存在,而后创建。
  2. 一开始 b\x.txt 不存在,而后依次创建。
  3. 从 y.txt 文件重命名到 x.txt。
  4. 一开始文件 x.txt 存在,而后删除,再然后重新创建。

不支持这些情况:

  1. 一开始文件存在,但你直接删除了 a 或者 b 文件夹,而不是先删除了 x.txt。
  2. 一开始文件存在,但直接将 b\x.txt 连文件带文件夹一起移走,然后删除文件或文件夹。
  3. 一开始 b\x.txt 都不存在,但现在保持文件夹结构连文件带文件夹一起移入到 a 文件夹中。

当然,也有一些意外的发现:

  1. 一开始文件存在,但直接将 b\x.txt 连文件带文件夹一起移走,这时依然能监听到 x.txt 文件的改变,但它已经不在原来的目录了。

附所有源码

如果看不到,请访问:FileWatcher that helps you to watch a single file change even if the file or it’s owner folers does not exists.。


参考资料

  • FileSystemWatcher Class (System.IO) - Microsoft Docs
  • c# - How can i use FileSystemWatcher to watch directory if directory not exist? - Stack Overflow
  • FileSystemWatcher - Pure Chaos (Part 1 of 2) - CodeProject
  • FileSystemWatcher - Pure Chaos (Part 2 of 2) - CodeProject
  • Recursive Directory Watch - CodeProject
  • How does Read File watch for changes? - Grasshopper
  • c# - Reading file after writing it - Stack Overflow

我的博客会首发于 https://walterlv.com/,而 CSDN 和博客园仅从其中摘选发布,而且一旦发布了就不再更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://blog.csdn.net/wpwalter),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

相关文章:

  • C#/.NET 使用 CommandLineParser 来标准化地解析命令行
  • .NET 中使用 TaskCompletionSource 作为线程同步互斥或异步操作的事件
  • 使用 WPF 开发一个 Windows 屏幕保护程序
  • 在 Windows 10 中开启移动 WLAN 热点
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • 在 Roslyn 分析语法树时添加条件编译符号的支持
  • 自然码的形码
  • 出于迁移项目的考虑,GitHub 中 Fork 出来的项目,如何与原项目断开 Fork 关系?
  • 只需 5 秒钟,你就能取到 WPF 程序的超高分辨率超高清截图
  • 谨慎使用 FileInfo.Exists 实例方法,而是使用 File.Exists 静态方法替代
  • UWP 在 WebView 中执行 JavaScript 代码(用于模拟用户输入等)
  • .NET 中使用 Mutex 进行跨越进程边界的同步
  • int? 竟然真的可以是 null!.NET/C# 确定可空值类型 NullableT 实例的真实类型
  • Slack 开发入门之 Incoming Webhooks:往 Slack 的 Channel 中发消息
  • 三值 bool? 进行与或运算后的结果
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • android高仿小视频、应用锁、3种存储库、QQ小红点动画、仿支付宝图表等源码...
  • ES6之路之模块详解
  • Javascript弹出层-初探
  • js中forEach回调同异步问题
  • js作用域和this的理解
  • React Native移动开发实战-3-实现页面间的数据传递
  • Spring核心 Bean的高级装配
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • XForms - 更强大的Form
  • 基于Mobx的多页面小程序的全局共享状态管理实践
  • 基于Vue2全家桶的移动端AppDEMO实现
  • 基于游标的分页接口实现
  • 将回调地狱按在地上摩擦的Promise
  • 使用 Xcode 的 Target 区分开发和生产环境
  • 跳前端坑前,先看看这个!!
  • 新书推荐|Windows黑客编程技术详解
  • 用jQuery怎么做到前后端分离
  • 在electron中实现跨域请求,无需更改服务器端设置
  • 在GitHub多个账号上使用不同的SSH的配置方法
  • 自定义函数
  • Nginx惊现漏洞 百万网站面临“拖库”风险
  • 湖北分布式智能数据采集方法有哪些?
  • # 深度解析 Socket 与 WebSocket:原理、区别与应用
  • #NOIP 2014# day.2 T2 寻找道路
  • (33)STM32——485实验笔记
  • (4)(4.6) Triducer
  • (附源码)springboot“微印象”在线打印预约系统 毕业设计 061642
  • (论文阅读笔记)Network planning with deep reinforcement learning
  • (七)理解angular中的module和injector,即依赖注入
  • (五)大数据实战——使用模板虚拟机实现hadoop集群虚拟机克隆及网络相关配置
  • (一)Java算法:二分查找
  • (转) RFS+AutoItLibrary测试web对话框
  • (转)Mysql的优化设置
  • (轉貼) 資訊相關科系畢業的學生,未來會是什麼樣子?(Misc)
  • .mat 文件的加载与创建 矩阵变图像? ∈ Matlab 使用笔记
  • .net core IResultFilter 的 OnResultExecuted和OnResultExecuting的区别
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...
  • .NET(C#) Internals: as a developer, .net framework in my eyes
  • .NET/C# 使用 SpanT 为字符串处理提升性能