Directory.EnumerateFiles 搜索模式不适用于文件共享
Directory.EnumerateFiles search pattern does not work on file shares
根据Microsoft Docs site for Directory.EnumerateFiles,搜索模式参数将匹配任何以指定模式开头且恰好为 3 个字符的扩展名。但是,这不适用于文件共享,仅适用于本地驱动器。
对于包含名为 file.xlsx
的单个文件的 \share\folder\
目录,第一个代码片段不 return 它:
public static List<string> GetAllFilesFromDirectory(string directory) =>
new[] { "*.csv", "*.xls", "*.txt" }.SelectMany(ext => Directory.EnumerateFiles(directory, ext)).ToList();
但是,如果我添加 *.xlsx
模式,它会 return 它:
public static List<string> GetAllFilesFromDirectory(string directory) =>
new[] { "*.csv", "*.xls", "*.xlsx", "*.txt" }.SelectMany(ext => Directory.EnumerateFiles(directory, ext)).ToList();
我还用 C:\temp
目录中的同一个文件测试了它,结果发现 return 对它进行了双向编辑。
这是 .NET Framework 4.7.2 控制台应用程序中的运行。
我是否遗漏了搜索模式中的某些内容?还是这不能像本地驱动器一样处理文件共享?这是意料之中的事吗?
你一定是最倒霉的人,碰上了这个bug。我可以确认它的行为符合您的观察,而且在互联网上的任何地方都找不到对此的任何引用。
所以我追踪了 .NET 源代码以查看 Directory.EnumerateFiles
是如何工作的,并且 - 在内心深处 - 最终 运行 变成了 call 到 FindFirstFile
以及后续FindNextFile
来电。这些是直接从内核调用的 PInvoked,所以你不能得到比这更低的了。
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);
那我们得测试一下。你猜怎么着?它会在本地目录中捕获 XLSX 文件,但不会在网络共享中捕获。
The doc for the function 也没有提到这种行为。嗯是的。您刚刚点击了一个未记录的“功能”:)
编辑:这变得更好了。 在 .NET Core(从 2.0 一直到 .NET 5)中看起来像这样行为不再存在。他们实际上写了自己的 pattern matcher this time round. *.xls
would not catch XLSX in any folders, local or otherwise. Yet their documentation 仍然说应该。
编辑 2021:doco 现在已经 updated 评论了 .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;
}
}
根据Microsoft Docs site for Directory.EnumerateFiles,搜索模式参数将匹配任何以指定模式开头且恰好为 3 个字符的扩展名。但是,这不适用于文件共享,仅适用于本地驱动器。
对于包含名为 file.xlsx
的单个文件的 \share\folder\
目录,第一个代码片段不 return 它:
public static List<string> GetAllFilesFromDirectory(string directory) =>
new[] { "*.csv", "*.xls", "*.txt" }.SelectMany(ext => Directory.EnumerateFiles(directory, ext)).ToList();
但是,如果我添加 *.xlsx
模式,它会 return 它:
public static List<string> GetAllFilesFromDirectory(string directory) =>
new[] { "*.csv", "*.xls", "*.xlsx", "*.txt" }.SelectMany(ext => Directory.EnumerateFiles(directory, ext)).ToList();
我还用 C:\temp
目录中的同一个文件测试了它,结果发现 return 对它进行了双向编辑。
这是 .NET Framework 4.7.2 控制台应用程序中的运行。
我是否遗漏了搜索模式中的某些内容?还是这不能像本地驱动器一样处理文件共享?这是意料之中的事吗?
你一定是最倒霉的人,碰上了这个bug。我可以确认它的行为符合您的观察,而且在互联网上的任何地方都找不到对此的任何引用。
所以我追踪了 .NET 源代码以查看 Directory.EnumerateFiles
是如何工作的,并且 - 在内心深处 - 最终 运行 变成了 call 到 FindFirstFile
以及后续FindNextFile
来电。这些是直接从内核调用的 PInvoked,所以你不能得到比这更低的了。
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);
那我们得测试一下。你猜怎么着?它会在本地目录中捕获 XLSX 文件,但不会在网络共享中捕获。
The doc for the function 也没有提到这种行为。嗯是的。您刚刚点击了一个未记录的“功能”:)
编辑:这变得更好了。 在 .NET Core(从 2.0 一直到 .NET 5)中看起来像这样行为不再存在。他们实际上写了自己的 pattern matcher this time round. *.xls
would not catch XLSX in any folders, local or otherwise. Yet their documentation 仍然说应该。
编辑 2021:doco 现在已经 updated 评论了 .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;
}
}