como você pode verificar facilmente se o acesso é negado para um arquivo no .NET?

100

Basicamente, gostaria de verificar se tenho direitos para abrir o arquivo antes de tentar abri-lo; Não quero usar um try / catch para essa verificação, a menos que seja necessário. Existe uma propriedade de acesso ao arquivo que posso verificar antes?

Horas
fonte
2
Legenda quando mudei a tag: "estou corrigindo". Não é brincadeira.
Joel Coehoorn
6
Concordo- eu gostaria que houvesse um TryOpen (ou seja, padrão Try-Parse).
Tristan

Respostas:

157

Já fiz isso inúmeras vezes no passado e, quase todas as vezes, errei ao tentar.

As permissões do arquivo (até mesmo a existência do arquivo) são voláteis - podem mudar a qualquer momento. Graças à Lei de Murphy, isso inclui especialmente o breve período entre quando você verifica o arquivo e quando tenta abri-lo. Uma mudança é ainda mais provável se você estiver em uma área onde sabe que precisa verificar primeiro. No entanto, estranhamente, isso nunca acontecerá em seus ambientes de teste ou desenvolvimento, que tendem a ser bastante estáticos. Isso torna o problema difícil de rastrear posteriormente e torna mais fácil para esse tipo de bug colocá-lo em produção.

O que isso significa é que você ainda precisa ser capaz de lidar com a exceção se as permissões ou a existência do arquivo forem ruins, apesar de sua verificação. O código de tratamento de exceção é necessário , independentemente de você verificar ou não as permissões do arquivo com antecedência. O código de tratamento de exceções fornece todas as funcionalidades de verificação de existência ou permissões. Além disso, embora manipuladores de exceção como este sejam conhecidos por serem lentos, é importante lembrar que a E / S do disco é ainda mais lenta ... muito mais lenta ... e chamar a função .Exists () ou verificar as permissões forçará uma viagem adicional o sistema de arquivos.

Em resumo, uma verificação inicial antes de tentar abrir o arquivo é redundante e desperdiça. Não há nenhum benefício adicional sobre o tratamento de exceções; isso realmente prejudicará, não ajudará, seu desempenho, acrescenta custos em termos de mais código que deve ser mantido e pode introduzir bugs sutis em seu código. Simplesmente não há nenhuma vantagem em fazer a verificação inicial. Em vez disso, a coisa certa aqui é apenas tentar abrir o arquivo e se esforçar em um bom manipulador de exceções se ele falhar. O mesmo é verdadeiro mesmo se você estiver apenas verificando se o arquivo existe ou não. Esse raciocínio se aplica a qualquer recurso volátil.

Joel Coehoorn
fonte
5
Exatamente. Este é um exemplo clássico de condição de corrida.
Powerlord
3
korro: você tem que ser capaz de lidar com permissões ruins em caso de falha de qualquer maneira, e isso torna a verificação inicial redundante e um desperdício.
Joel Coehoorn
2
Uma verificação inicial pode ajudar a lidar com erros específicos comuns de maneira adequada - olhar para o futuro geralmente é mais fácil do que combinar atributos de exceção particulares com causas específicas. O try / catch ainda permanece obrigatório.
Peterchen,
5
Essa resposta não responde à pergunta "como verificar se tenho direitos para abrir o arquivo" em alguma instância antes de tentar abri-lo. O caso pode muito bem ser que, se a permissão não for permitida nessa instância, o software não tentará ler o arquivo, mesmo que a permissão possa muito bem ser concedida logo após as permissões terem sido verificadas.
Triynko
5
Não importa se as permissões são voláteis, quando você só se importa com o que são naquele instante. A falha sempre deve ser tratada, mas se você verificar se há uma permissão de leitura e ela não estiver lá, você pode querer pular a leitura do arquivo, mesmo se for possível que um segundo depois você tenha acesso. Você tem que desenhar a linha em algum lugar.
Triynko
25

Dica rápida para qualquer pessoa que venha aqui com um problema semelhante:

Cuidado com os aplicativos de sincronização da web, como o DropBox. Acabei de passar 2 horas pensando que a instrução "using" (Dispose pattern) está quebrada no .NET.

Acabei percebendo que o Dropbox está continuamente lendo e gravando arquivos em segundo plano, para sincronizá-los.

Adivinha onde minha pasta Projetos do Visual Studio está localizada? Dentro da pasta "Meu Dropbox", é claro.

Portanto, conforme eu executava meu aplicativo no modo de depuração, os arquivos que ele estava lendo e gravando também eram continuamente acessados ​​pelo DropBox para serem sincronizados com o servidor DropBox. Isso causou os conflitos de bloqueio / acesso.

Portanto, pelo menos agora sei que preciso de uma função File Open mais robusta (ou seja, TryOpen () que fará várias tentativas). Estou surpreso que ainda não seja parte integrante da estrutura.

[Atualizar]

Esta é minha função auxiliar:

/// <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;
}
Cinza
fonte
3
@Ash Eu acho que você não leu a pergunta corretamente ELE quer evitar tentar pegar.
Ravisha
10
@Ravisha, você ao menos leu a resposta mais votada de Joel? Como Joel diz, "O que você faz é apenas tentar abrir o arquivo e lidar com a exceção se ela falhar" . Por favor, não downvote só porque você não gosta do fato de que algo não pode ser evitado.
Ash
Obrigado pelo código! Uma coisa, talvez seja melhor usar usando, por exemplo, veja a resposta da Tazeem aqui
Cel
Se você devolver o fluxo de arquivos, o usingterá que ser usado pelo autor da chamada, embora ...
Cel
@Cel - usingnão funcionará aqui. No final do bloco de uso, fsserá fechado à força. Você dará ao chamador um filestream FECHADO (tão inútil)!
Toolmaker Steve
4

Aqui está a solução que você está procurando

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

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

isso cria uma nova permissão de leitura com base na visualização do caminho de todos os arquivos e verifica se é igual ao acesso ao arquivo de leitura.

dj shahar
fonte
3

Primeiro, o que Joel Coehoorn disse.

Além disso: você deve examinar as suposições que estão por trás do seu desejo de evitar o uso de try / catch, a menos que seja necessário. O motivo típico para evitar a lógica que depende de exceções (a criação de Exceptionobjetos tem um desempenho ruim) provavelmente não é relevante para o código que está abrindo um arquivo.

Suponho que se você está escrevendo um método que preenche um List<FileStream>abrindo todos os arquivos em uma subárvore de diretório e espera que um grande número deles esteja inacessível, você pode querer verificar as permissões de arquivo antes de tentar abrir um arquivo para que não obter muitas exceções. Mas você ainda lidaria com a exceção. Além disso, provavelmente há algo terrivelmente errado com o design do seu programa se você estiver escrevendo um método que faça isso.

Robert Rossney
fonte
-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;
        }
Omid Matouri
fonte
-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);
        }
    }
}
Rudzitis
fonte
8
-1: use "jogar;" não "lançar unauthorizedAccessException;". Você está perdendo o rastreamento da pilha.
John Saunders,
Por que é attemptspassado por ref? Isso não faz sentido. Nem o teste para em <=vez de apenas ==.
Konrad Rudolph,
1
@John: bem, neste caso é desejável perder o rastreamento de pilha (profundamente aninhado) da chamada recursiva, então acho que neste caso throw exé realmente a coisa certa a fazer.
Konrad Rudolph,
2
@Konrad: @Rudzitis: Estou mudando meu motivo para -1. É pior do que bagunçar a pilha por "jogar ex". Você está estragando a pilha ao induzir artificialmente níveis extras de pilha por meio de recursão em um momento em que a profundidade da pilha realmente importa. Este é um problema iterativo, não recursivo.
John Saunders