как вы можете легко проверить, запрещен ли доступ к файлу в .NET?

100

По сути, я хотел бы проверить, есть ли у меня права на открытие файла, прежде чем я действительно попытаюсь его открыть; Я не хочу использовать для этой проверки команду try / catch, если только в этом нет необходимости. Есть ли свойство доступа к файлам, которое я могу проверить заранее?

Horas
источник
2
Подпись при изменении тега: «Я исправляю». Не шутка.
Джоэл Кохорн
6
Согласовано - я бы хотел, чтобы не было TryOpen (т.е. шаблон Try-Parse).
Тристан

Ответы:

157

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

Права доступа к файлам (даже наличие файла) непостоянны - они могут измениться в любое время. Благодаря закону Мерфи это особенно касается короткого периода между проверкой файла и попыткой его открытия. Изменение еще более вероятно, если вы находитесь в районе, где, как вы знаете, вам нужно сначала проверить. Но, как ни странно, этого никогда не произойдет в ваших средах тестирования или разработки, которые обычно довольно статичны. Это затрудняет последующее отслеживание проблемы и упрощает внедрение этой ошибки в производственную среду.

Это означает, что вы все равно должны иметь возможность обрабатывать исключение, если права доступа или существование файла плохие, несмотря на вашу проверку. Код обработки исключений требуется независимо от того, проверили ли вы разрешения для файла заранее. Код обработки исключений предоставляет все функции проверки существования или разрешений. Кроме того, хотя такие обработчики исключений, как известно, работают медленно, важно помнить, что ввод-вывод диска еще медленнее ... намного медленнее ... и вызов функции .Exists () или проверка разрешений приведет к дополнительному отключению из файловой системы.

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

Джоэл Кохорн
источник
5
Именно. Это классический пример состояния гонки.
Powerlord 05
3
korro: в любом случае вы должны иметь возможность обрабатывать неверные разрешения при сбое, а это делает первоначальную проверку излишней и расточительной.
Joel Coehoorn
2
Первоначальная проверка может помочь аккуратно обработать типичные специфические ошибки - забегать вперед часто проще, чем сопоставить определенные атрибуты исключения с конкретными причинами. Попытка / улов по-прежнему остается обязательной.
peterchen
5
Этот ответ не отвечает на вопрос «как проверить, есть ли у меня права на открытие файла» в какой-то момент перед попыткой его открытия. Вполне может быть, что если разрешение не разрешено в этом случае, программное обеспечение не будет пытаться прочитать файл, даже если разрешение вполне может быть предоставлено сразу после проверки разрешений.
Трийнко
5
Не имеет значения, являются ли разрешения нестабильными, если вас волнует только то, что они есть в данный момент. Сбой всегда следует обрабатывать, но если вы проверяете разрешение на чтение, а его там нет, вы можете пропустить чтение файла, даже если возможно, что через секунду у вас может быть доступ. Вы должны где-нибудь провести черту.
Трийнко
25

Подсказка для тех, кто приходит сюда с похожей проблемой:

Не упустите приложения для веб-синхронизации, такие как DropBox. Я просто потратил 2 часа, думая, что оператор "using" (шаблон Dispose) не работает в .NET.

В конце концов я понял, что Dropbox постоянно читает и записывает файлы в фоновом режиме, чтобы синхронизировать их.

Угадайте, где находится моя папка проектов Visual Studio? Конечно, внутри папки «Мой Dropbox».

Поэтому, когда я запускал свое приложение в режиме отладки, DropBox постоянно обращался к файлам, которые он читал и записывал, для синхронизации с сервером DropBox. Это вызвало конфликты блокировки / доступа.

По крайней мере, теперь я знаю, что мне нужна более надежная функция открытия файла (например, TryOpen (), которая сделает несколько попыток). Я удивлен, что это еще не встроенная часть фреймворка.

[Обновить]

Вот моя вспомогательная функция:

/// <summary>
/// Tries to open a file, with a user defined number of attempt and Sleep delay between attempts.
/// </summary>
/// <param name="filePath">The full file path to be opened</param>
/// <param name="fileMode">Required file mode enum value(see MSDN documentation)</param>
/// <param name="fileAccess">Required file access enum value(see MSDN documentation)</param>
/// <param name="fileShare">Required file share enum value(see MSDN documentation)</param>
/// <param name="maximumAttempts">The total number of attempts to make (multiply by attemptWaitMS for the maximum time the function with Try opening the file)</param>
/// <param name="attemptWaitMS">The delay in Milliseconds between each attempt.</param>
/// <returns>A valid FileStream object for the opened file, or null if the File could not be opened after the required attempts</returns>
public FileStream TryOpen(string filePath, FileMode fileMode, FileAccess fileAccess,FileShare fileShare,int maximumAttempts,int attemptWaitMS)
{
    FileStream fs = null;
    int attempts = 0;

    // Loop allow multiple attempts
    while (true)
    {
        try
        {
            fs = File.Open(filePath, fileMode, fileAccess, fileShare);

            //If we get here, the File.Open succeeded, so break out of the loop and return the FileStream
            break;
        }
        catch (IOException ioEx)
        {
            // IOExcception is thrown if the file is in use by another process.

            // Check the numbere of attempts to ensure no infinite loop
            attempts++;
            if (attempts > maximumAttempts)
            {
                // Too many attempts,cannot Open File, break and return null 
                fs = null;
                break;
            }
            else
            {
                // Sleep before making another attempt
                Thread.Sleep(attemptWaitMS);

            }

        }

    }
    // Reutn the filestream, may be valid or null
    return fs;
}
Ясень
источник
3
@Ash Я думаю, ты не прочитал вопрос должным образом. ОН хочет избежать попытки поймать.
Равиша,
10
@ Равиша, ты вообще читал самый популярный ответ Джоэла? Как говорит Джоэл: «Вместо этого вы просто пытаетесь открыть файл и обработать исключение в случае неудачи» . Пожалуйста, не голосуйте против только потому, что вам не нравится тот факт, что чего-то нельзя избежать.
Эш
Спасибо за код! Одна вещь, может быть лучше использовать using, например, см. Ответ Tazeem здесь
Cel
Если вы вернете поток файлов, то вызывающий usingдолжен будет использовать его ...
Cel
@Cel - usingздесь работать не будет. В конце блока использования fsбудет принудительно закрыто. Вы передадите вызывающей стороне ЗАКРЫТЫЙ (бесполезный) поток файлов!
ToolmakerSteve
4

Вот решение, которое вы ищете

var fileIOPermission = new FileIOPermission(FileIOPermissionAccess.Read,
                                            System.Security.AccessControl.AccessControlActions.View,
                                            MyPath);

if (fileIOPermission.AllFiles == FileIOPermissionAccess.Read)
{
    // Do your thing here...
}

это создает новое разрешение на чтение на основе представления пути ко всем файлам, а затем проверяет, равно ли оно чтению доступа к файлу.

DJ Shahar
источник
3

Первое, что сказал Джоэл Кохорн.

Также: вам следует изучить предположения, которые лежат в основе вашего желания избегать использования try / catch, если вам не нужно. Типичная причина отказа от логики, зависящей от исключений (создание Exceptionобъектов плохо работает), вероятно, не имеет отношения к коду, открывающему файл.

Я полагаю, что если вы пишете метод, который заполняет List<FileStream>, открывая каждый файл в поддереве каталога, и вы ожидали, что большое их количество будет недоступно, вы можете проверить права доступа к файлу, прежде чем пытаться открыть файл, чтобы вы не получить слишком много исключений. Но вы все равно справитесь с исключением. Кроме того, вероятно, что-то ужасно не так с дизайном вашей программы, если вы пишете метод, который делает это.

Роберт Россни
источник
-1
public static bool IsFileLocked(string filename)
        {
            bool Locked = false;
            try
            {
                FileStream fs =
                    File.Open(filename, FileMode.OpenOrCreate,
                    FileAccess.ReadWrite, FileShare.None);
                fs.Close();
            }
            catch (IOException ex)
            {
                Locked = true;
            }
            return Locked;
        }
Омид Матури
источник
-3
public static FileStream GetFileStream(String filePath, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, ref int attempts, int attemptWaitInMilliseconds)
{            
    try
    {
         return File.Open(filePath, fileMode, fileAccess, fileShare);
    }
    catch (UnauthorizedAccessException unauthorizedAccessException)
    {
        if (attempts <= 0)
        {
            throw unauthorizedAccessException;
        }
        else
        {
            Thread.Sleep(attemptWaitInMilliseconds);
            attempts--;
            return GetFileStream(filePath, fileMode, fileAccess, fileShare, ref attempts, attemptWaitInMilliseconds);
        }
    }
}
Рудзитис
источник
8
-1: используйте "бросить;" не "выбросить unauthorizedAccessException;". Вы теряете трассировку стека.
Джон Сондерс
Почему attemptsпропущено по исх? Это не имеет смысла. Также не проводится тестирование <=вместо просто ==.
Конрад Рудольф
1
@John: ну, в этом случае желательно потерять (глубоко вложенную) трассировку стека рекурсивного вызова, поэтому я думаю, что в этом случае throw exэто действительно правильное решение.
Конрад Рудольф
2
@Konrad: @Rudzitis: Я меняю причину появления -1. Это хуже, чем накрутить стек по "throw ex". Вы забиваете стек, искусственно вызывая дополнительные уровни стека посредством рекурсии, в то время как глубина стека действительно имеет значение. Это итеративная проблема, а не рекурсивная.
Джон Сондерс