File.Exists() неверно возвращает false, когда путь слишком длинный

В настоящее время я работаю над программой, которая проходит через различные каталоги, чтобы обеспечить наличие определенных файлов с помощью File.Exists().

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

Я понимаю, что есть вопросы о SO, которые обращаются к File.Exists(), возвращая неверные значения, но никто, кажется, не решает эту проблему.

Переименование каталогов и файлов для сокращения пути на самом деле не является вариантом, поэтому я не уверен, что делать в этот момент. Есть ли проблема, которая поможет решить эту проблему?

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

    private void checkFile(string path)
    {
        if (!File.Exists(path))
            Console.WriteLine("   *  File: " + path + " does not exist.");
    }

Ответ 1

Это уродливое и неэффективное, но оно ограничивает ограничение MAX_PATH:

const int MAX_PATH = 260;

private static void checkPath(string path)
{
    if (path.Length >= MAX_PATH)
    {
        checkFile_LongPath(path);
    }
    else if (!File.Exists(path))
    {
        Console.WriteLine("   *  File: " + path + " does not exist.");
    }
}

И вот функция checkFile_LongPath:

private static void checkFile_LongPath(string path)
{
    string[] subpaths = path.Split('\\');
    StringBuilder sbNewPath = new StringBuilder(subpaths[0]);
    // Build longest subpath that is less than MAX_PATH characters
    for (int i = 1; i < subpaths.Length; i++)
    {
        if (sbNewPath.Length + subpaths[i].Length >= MAX_PATH)
        {
            subpaths = subpaths.Skip(i).ToArray();
            break;
        }
        sbNewPath.Append("\\" + subpaths[i]);
    }
    DirectoryInfo dir = new DirectoryInfo(sbNewPath.ToString());
    bool foundMatch = dir.Exists;
    if (foundMatch)
    {
        // Make sure that all of the subdirectories in our path exist.
        // Skip the last entry in subpaths, since it is our filename.
        // If we try to specify the path in dir.GetDirectories(), 
        // We get a max path length error.
        int i = 0;
        while(i < subpaths.Length - 1 && foundMatch)
        {
            foundMatch = false;
            foreach (DirectoryInfo subDir in dir.GetDirectories())
            {
                if (subDir.Name == subpaths[i])
                {
                    // Move on to the next subDirectory
                    dir = subDir;
                    foundMatch = true;
                    break;
                }
            }
            i++;
        }
        if (foundMatch)
        {
            foundMatch = false;
            // Now that we've gone through all of the subpaths, see if our file exists.
            // Once again, If we try to specify the path in dir.GetFiles(), 
            // we get a max path length error.
            foreach (FileInfo fi in dir.GetFiles())
            {
                if (fi.Name == subpaths[subpaths.Length - 1])
                {
                    foundMatch = true;
                    break;
                }
            }
        }
    }
    // If we didn't find a match, write to the console.
    if (!foundMatch)
    {
        Console.WriteLine("   *  File: " + path + " does not exist.");
    }
}

Ответ 2

От MSDN - Именование файлов, путей и пространств имен:

В API Windows (с некоторыми исключениями, описанными ниже абзацы), максимальная длина для пути - MAX_PATH, что определяется как 260 символов.

...

В API Windows много функций, которые также имеют версии Unicode для разрешить длину с расширенной длиной для максимальной общей длины пути 32 767 символов. Этот тип пути состоит из компонентов разделенные обратными косыми чертами, каждая до значения, возвращаемого в Параметр lpMaximumComponentLength для GetVolumeInformation (это значение обычно составляет 255 символов). Чтобы указать расширенный путь, используйте префикс "\\?\". Например, "\\?\D:\very long path".

...

Поскольку вы не можете использовать префикс "\\?\" с относительным путем, относительные пути всегда ограничены общим количеством символов MAX_PATH.

(добавлен акцент)

Если все ваши пути являются полными путями, вы можете обновить свой код, чтобы использовать спецификатор пути расширенной длины следующим образом:

const longPathSpecifier = @"\\?";

private void checkFile(string path)
{
    // Add the long-path specifier if it missing
    string longPath = (path.StartsWith(longPathSpecifier) ? path : longPathSpecifier  + path);

    if (!File.Exists(longPath))
    {
        // Print the original path
         Console.WriteLine("   *  File: " + path + " does not exist.");
    }
}

Update:

Для ввода/вывода файлов префикс "\? \" к строке пути сообщает Windows API, чтобы отключить весь синтаксический анализ строк и отправить следующую строку это прямо в файловой системе. Например, , если файловая система поддерживает большие пути и имена файлов, вы можете превысить MAX_PATH лимиты, которые в ином случае применяются API Windows.

По крайней мере, в моей системе (с использованием Windows 7) длинные имена файлов не поддерживаются, поэтому я не могу проверить, будет ли это решение работать для вас.

Обновление: Я нашел решение, которое работает, но оно довольно уродливое. Вот что я сделал в псевдокоде:

  • Разделить путь в массив каталогов
  • Получить самую длинную часть вашего пути, которая составляет менее 260 символов (MAX_PATH).
  • Создайте DirectoryInfo для этой части вашего пути ( "dir" для дальнейшего использования).
  • Для остальных каталогов на вашем пути:
    а. Вызовите dir.GetDirectories() и проверьте, содержится ли следующий каталог в результатах поиска б. если это так, установите dir на DirectoryInfo и продолжайте копать с. если нет, то путь не существует
  • Как только мы перейдем ко всем каталогам, ведущим к нашему файлу, вызовите dir.GetFiles() и посмотрите, существует ли наш файл в возвращаемых объектах FileInfo.

Ответ 3

Никогда не возникала проблема, кто-то из другого сообщения SO предлагал открыть дескриптор файла, тем самым избегая в целом проверки "существует". Не уверен, что эта проблема по-прежнему имеет значение "long filename":

Это второй ответ здесь:

Проверьте, существует ли файл/каталог: есть ли лучший способ?

Не уверен, что это полезно: P

Ответ 4

Вам нужно P/Invoke API Win32, чтобы это нормально работало:

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern uint GetFileAttributes(string lpFileName);

    public static bool DirectoryExists(string path)
    {
        uint attributes = GetFileAttributes(path.StartsWith(@"\\?\") ? path : @"\\?\" + path);
        if (attributes != 0xFFFFFFFF)
        {
            return ((FileAttributes)attributes).HasFlag(FileAttributes.Directory);
        }
        else
        {
            return false;
        }
    }

    public static bool FileExists(string path)
    {
        uint attributes = GetFileAttributes(path.StartsWith(@"\\?\") ? path : @"\\?\" + path);
        if (attributes != 0xFFFFFFFF)
        {
            return !((FileAttributes)attributes).HasFlag(FileAttributes.Directory);
        }
        else
        {
            return false;
        }
    }

Ответ 5

Проверьте

  1. Манифест разрешений
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  1. Используйте файловый провайдер для создания и доступа к файлу
    <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="your_package.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_list" />
    </provider>
  1. содержимое file_list:
    <?xml version="1.0" encoding="utf-8"?>
    <paths>
        <external-path
            name="external"
            path="." />
        <external-files-path
            name="external_files"
            path="." />
        <cache-path
            name="cache"
            path="." />
        <external-cache-path
            name="external_cache"
            path="." />
        <files-path
            name="files"
            path="." />
    </paths>
  1. Держите ваше имя файла коротким