首页 > 解决方案 > Directory.EnumerateFiles 搜索模式不适用于文件共享

问题描述

根据Directory.EnumerateFiles 的 Microsoft Docs 站点,搜索模式参数将匹配以指定模式开头的任何扩展名,当它正好是 3 个字符时。但是,这不适用于文件共享,仅适用于本地驱动器。

对于\\share\folder\包含名为 的单个文件的目录file.xlsx,第一个代码片段不会返回它:

public static List<string> GetAllFilesFromDirectory(string directory) =>
   new[] { "*.csv", "*.xls", "*.txt" }.SelectMany(ext => Directory.EnumerateFiles(directory, ext)).ToList();

但是,如果我添加*.xlsx模式,它会返回它:

public static List<string> GetAllFilesFromDirectory(string directory) =>
   new[] { "*.csv", "*.xls", "*.xlsx", "*.txt" }.SelectMany(ext => Directory.EnumerateFiles(directory, ext)).ToList();

我还用目录中的相同文件对此进行了测试C:\temp,发现它以两种方式返回。

这是在 .NET Framework 4.7.2 控制台应用程序中运行的。

我在搜索模式中遗漏了什么吗?或者这不适用于文件共享与本地驱动器相同的方式?这是可以预期的吗?

标签: c#.net.net-4.7.2

解决方案


你一定是最倒霉的人,碰到了这个bug。我可以确认它的行为符合您的观察,并且在互联网上的任何地方都找不到对此的任何引用。

因此,我跟踪了 .NET 源代码以了解其Directory.EnumerateFiles工作原理,并且 - 在内心深处 - 最终遇到了调用FindFirstFile后续FindNextFile调用。这些是直接从内核中调用的,所以你不能得到比这更低的值。

[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);

那得测试一下。你猜怎么了?它在本地目录中捕获 XLSX 文件,但不在网络共享中。

该函数的文档也没有提到这种行为。是的。您刚刚遇到了一个未记录的“功能”:)

编辑:这变得更好了。看起来在.NET Core(从 2.0 一直到 .NET 5)这种行为不再存在。这次他们实际上编写了自己的模式匹配器。*.xls不会在任何本地或其他文件夹中捕获 XLSX。然而他们的文件仍然说它应该。

2021 年编辑:该文档现已更新,其中包含有关 .NET Framework 怪癖的评论。


这是我的通话测试代码FindFirstFile

public class Program
{
    public static void Main(string[] args)
    {
        // Ensure these test folders only contain ONE file.
        // Name the file "Test.xlsx"
        Test(@"C:\Temp\*.xls"); // Finds the xlsx file just fine
        Test(@"\\Server\Temp\*.xls"); // But not here!
    }

    public static void Test(string fileName)
    {
        Win32Native.WIN32_FIND_DATA data;
        var hnd = Win32Native.FindFirstFile(fileName, out data);

        if (hnd == Win32Native.InvalidPtr) 
            Debug.WriteLine("Not found!!");
        else
            Debug.WriteLine("Found: " + data.cFileName);
    }
}

/** Windows native Pinvoke **/
public class Win32Native
{
    public static IntPtr InvalidPtr = new IntPtr(-1);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    public struct WIN32_FIND_DATA
    {
        public uint dwFileAttributes;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
        public uint nFileSizeHigh;
        public uint nFileSizeLow;
        public uint dwReserved0;
        public uint dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
        public string cAlternateFileName;
    }
}

推荐阅读