当你需要监视文件或文件夹的改变的时候,使用 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.


参考资料


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

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

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

  1. 新建childTest文件夹,里面依然放进我们需要的.py文件即可

    一.模块 我们编写文件:a.py,放在C:\Python34\Lib\sit-packages下,里面写上一句代码为: print('this is a') 之后我们就可以在我们的代码里面引用a.py ...

  2. 2. pushd . :将当前文件夹压入栈,使用popd能够回到该文件夹。

    1.man -t  ls | ps2pdf -> ls.pdf生成pdf格式的ls帮助文件. 2. pushd . :将当前文件夹压入栈,使用popd能够回到该文件夹. 3.find -type ...

  3. ASP.NET Core 上传文件到共享文件夹

    参考资料:ASP.NET 上传文件到共享文件夹 创建共享文件夹参考资料:https://www.cnblogs.com/dansediao/p/5712657.html 一.配置上传文件相关参数并读取 ...

  4. ASP.NET 上传文件到共享文件夹

    创建共享文件夹参考资料:https://www.cnblogs.com/dansediao/p/5712657.html 上传文件代码 web.config <!--上传文件配置,UploadP ...

  5. 编写Java程序,在一个文件夹内,查找占用磁盘空间最大的 jpg 文件,并输出文件大小

    查看本章节 查看作业目录 需求说明: 在一个文件夹内,查找占用磁盘空间最大的 jpg 文件,并输出文件大小 实现思路: 创建ImageFileFilter类实现FilenameFilter接口,且重写 ...

  6. Wince 6.0适用 .NET 使用HttpRequest的Post上传文件,服务端的Web API接收Post上传上来的文件 代码

    //调用的示例 private string fileName = "InStorageData.csv"; string filePath = parentPath + Comm ...

  7. C#使用FileSystemWatcher控件实现的文件监控功能示例

    本文实例讲述了C#使用FileSystemWatcher控件实现的文件监控功能.分享给大家供大家参考,具体如下: FileSystemWatcher 可以使用FileSystemWatcher组件监视 ...

  8. 程序一 用记事本建立文件src.dat,其中存放若干字符。编写程序,从文件src.dat中读取数据,统计其中的大写字母、小写字母、数字、其它字符的个数,并将这些数据写入到文件test.dat中。

    用记事本建立文件src.dat,其中存放若干字符.编写程序,从文件src.dat中读取数据,统计其中的大写字母.小写字母.数字.其它字符的个数,并将这些数据写入到文件test.dat中. #inclu ...

  9. xilinx Vivado的使用详细介绍(2):创建工程、添加文件、综合、实现、管脚约束、产生比特流文件、烧写程序、硬件验证

    xilinx Vivado的使用详细介绍(2):创建工程.添加文件.综合.实现.管脚约束.产生比特流文件.烧写程序.硬件验证 Author:zhangxianhe 新建工程 打开Vivado软件,直接 ...

随机推荐

  1. 探索gff/gtf格式

    参考: GFF格式说明 Generic Feature Format Version 3 (GFF3) 先下载一个 gtf 文件浏览一下 1 havana gene 11869 14409 . + . ...

  2. English trip -- Review Unit 10 Leisure 休闲

    Words dance exercise fish play basketball play cards swim cook play the guitar listen to music watch ...

  3. spoj Help the Military Recruitment Office!

    题意:给出名字和地方,地方会重定向,最后再给出名字,问现在属于哪里? 用并查集. //#pragma comment(linker,"/STACK:1024000000,1024000000 ...

  4. Jmeter响应中中文乱码解决办法

    在jmeter的bin目录下有一个jmeter.properties的文件,打开它,搜索sampleresult.default.encoding,把它的注释打开,也就是把最前面的#去掉,改成samp ...

  5. HDU1789时间贪心

    Doing Homework again Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Oth ...

  6. datafile相关(add、rename、drop)

    --case 1 add14:25:04 FPYJ(150_9)@test> alter tablespace fpyj_data02 add datafile '/oradata02/test ...

  7. SQL语句增加列、修改列类型、修改列、删除列

    1.增加列: alter table tableName add columnName varchar(30) 2.修改列类型: alter table tableName alter column ...

  8. JAVA 中CLOB与Clob有区别

    在JAVA中CLOB与Clob是有区别的类型. (oracle.jdbc.internal.OracleCallableStatement)OracleCallableStatement能接收CLOB ...

  9. 通过AO连接多个EO并进行使用

    参考资料 在toolbox下的Tutorial工程里面 \oracle\apps\fnd\framework\toolbox\tutorial\server\PurchaseOrdersSVO

  10. 算法笔记——C/C++语言基础篇(已完结)

    开始系统学习算法,希望自己能够坚持下去,期间会把常用到的算法写进此博客,便于以后复习,同时希望能够给初学者提供一定的帮助,手敲难免存在错误,欢迎评论指正,共同学习.博客也可能会引用别人写的代码,如有引 ...