Шаблон поиска Directory.EnumerateFiles не работает в файловых ресурсах

Согласно Сайт Microsoft Docs для Directory.EnumerateFiles, параметр шаблона поиска будет соответствовать любому расширению, начинающемуся с указанного шаблона, если оно содержит ровно 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.

Я что-то пропустил в шаблоне поиска? Или это не работает с файловыми ресурсами так же, как с локальными дисками? Ожидается ли это?


person Scott Hoffman    schedule 28.10.2020    source источник


Ответы (1)


Вы, должно быть, самый неудачливый человек, раз наткнулись на эту ошибку. Я могу подтвердить, что он ведет себя согласно вашему наблюдению, а также не смог найти никаких упоминаний об этом нигде в Интернете.

Итак, я проследил исходный код .NET, чтобы увидеть, как работает Directory.EnumerateFiles, и — глубоко в недрах — в конце концов наткнулся на /filesystemenumerable.cs#L272" rel="nofollow noreferrer">вызов к FindFirstFile и последующим вызовам FindNextFile. Они были PInvoked непосредственно из ядра, так что вы не можете получить что-то ниже этого.

[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;
    }
}
person NPras    schedule 28.10.2020
comment
Хорошее расследование. Интересно, увидели бы мы разницу, если бы явным образом использовали расширенные версии вызовов? Я подозреваю, что в зависимости от того, что используется, он вызывает устаревший код, который не знает длинного имени файла и каким-то образом усекает расширение. - person Jeff Mercado; 28.10.2020