comment pouvez-vous vérifier facilement si l'accès est refusé pour un fichier dans .NET?

100

En gros, je voudrais vérifier si j'ai le droit d'ouvrir le fichier avant d'essayer de l'ouvrir; Je ne veux pas utiliser un try / catch pour cette vérification, sauf si je dois le faire. Y a-t-il une propriété d'accès aux fichiers que je peux vérifier à l'avance?

Horas
la source
2
Légende lorsque j'ai changé le tag: "im correcting". Sans blague.
Joel Coehoorn
6
D'accord - Je souhaite qu'il y ait un TryOpen (c'est-à-dire un modèle Try-Parse).
Tristan le

Réponses:

157

J'ai fait cela d'innombrables fois dans le passé, et presque chaque fois que je l'ai fait, j'avais même eu tort d'essayer.

Les autorisations de fichier (même l'existence de fichier) sont volatiles - elles peuvent changer à tout moment. Grâce à la loi de Murphy, cela inclut en particulier la brève période entre le moment où vous vérifiez le fichier et le moment où vous essayez de l'ouvrir. Un changement est encore plus probable si vous vous trouvez dans une zone où vous savez que vous devez d'abord vérifier. Pourtant, étrangement, cela ne se produira jamais dans vos environnements de test ou de développement, qui ont tendance à être assez statiques. Cela rend le problème difficile à repérer plus tard et facilite la mise en production de ce type de bogue.

Cela signifie que vous devez toujours être en mesure de gérer l'exception si les autorisations de fichier ou l'existence sont mauvaises, malgré votre vérification. Un code de gestion des exceptions est requis , que vous vérifiiez ou non les autorisations du fichier à l'avance. Le code de gestion des exceptions fournit toutes les fonctionnalités de vérification d'existence ou d'autorisations. De plus, alors que les gestionnaires d'exceptions comme celui-ci sont connus pour être lents, il est important de se rappeler que les E / S disque sont encore plus lentes ... beaucoup plus lentes ... et l'appel de la fonction .Exists () ou la vérification des autorisations forcera un déclenchement supplémentaire. le système de fichiers.

En résumé, une vérification initiale avant d'essayer d'ouvrir le fichier est à la fois redondante et inutile. Il n'y a pas d'avantage supplémentaire par rapport à la gestion des exceptions, cela nuira en fait, pas aider, vos performances, cela ajoute des coûts en termes de code supplémentaire qui doit être maintenu, et cela peut introduire des bogues subtils dans votre code. Il n'y a tout simplement aucun avantage à faire la vérification initiale. Au lieu de cela, la bonne chose ici est d'essayer simplement d'ouvrir le fichier et de mettre vos efforts dans un bon gestionnaire d'exceptions en cas d'échec. La même chose est vraie même si vous ne faites que vérifier si le fichier existe ou non. Ce raisonnement s'applique à toute ressource volatile.

Joel Coehoorn
la source
5
Exactement. Ceci est un exemple classique d'une condition de course.
Powerlord
3
korro: vous devez quand même être capable de gérer les mauvaises autorisations en cas d'échec, ce qui rend le contrôle initial redondant et inutile.
Joel Coehoorn
2
Une vérification initiale peut aider à gérer les erreurs spécifiques courantes avec élégance - il est souvent plus facile de regarder vers l'avenir que de faire correspondre des attributs d'exception particuliers à des causes spécifiques. Le try / catch reste toujours obligatoire.
peterchen
5
Cette réponse ne répond pas à la question «comment vérifier si j'ai le droit d'ouvrir le fichier» à un moment donné avant d'essayer de l'ouvrir. Le cas peut très bien être que si l'autorisation n'est pas autorisée à cet instant, le logiciel n'essaiera pas de lire le fichier, même si l'autorisation peut très bien être accordée juste après que les autorisations ont été vérifiées.
Triynko
5
Peu importe que les autorisations soient volatiles, lorsque vous ne vous souciez que de ce qu'elles sont à cet instant. L'échec doit toujours être géré, mais si vous vérifiez une autorisation de lecture et qu'elle n'est pas là, vous pouvez ignorer la lecture du fichier, même s'il est possible qu'une seconde plus tard, vous y ayez accès. Vous devez tracer la ligne quelque part.
Triynko
25

Astuce rapide pour quiconque vient ici avec un problème similaire:

Méfiez-vous des applications de synchronisation Web telles que DropBox. Je viens de passer 2 heures à penser que l'instruction "using" (Dispose pattern) est cassée dans .NET.

J'ai finalement réalisé que Dropbox lisait et écrivait continuellement des fichiers en arrière-plan, afin de les synchroniser.

Devinez où se trouve mon dossier Projets Visual Studio? Dans le dossier "My Dropbox" bien sûr.

Par conséquent, lorsque j'exécutais mon application en mode Débogage, les fichiers qu'elle lisait et écrivait étaient également continuellement accédés par DropBox pour être synchronisés avec le serveur DropBox. Cela a provoqué les conflits de verrouillage / accès.

Donc au moins je sais maintenant que j'ai besoin d'une fonction d'ouverture de fichier plus robuste (ie TryOpen () qui fera plusieurs tentatives). Je suis surpris que ce ne soit pas déjà une partie intégrée du cadre.

[Mettre à jour]

Voici ma fonction d'assistance:

/// <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;
}
Cendre
la source
3
@Ash Je pense que vous n'avez pas lu la question correctement IL veut éviter d'essayer de prendre.
Ravisha
10
@Ravisha, avez-vous même lu la réponse la plus votée de Joel? Comme le dit Joel, "ce que vous faites à la place est simplement d'essayer d'ouvrir le fichier et de gérer l'exception en cas d'échec" . Veuillez ne pas voter simplement parce que vous n'aimez pas le fait que quelque chose ne peut être évité.
Ash
Merci pour le code! Une chose, il serait peut-être préférable d'utiliser en utilisant, par exemple, voir la réponse de Tazeem ici
Cel
Étant donné que vous renvoyez le flux de fichiers, l'appelant usingdevra cependant être utilisé par l'appelant ...
Cel
@Cel - usingne fonctionnera pas ici. À la fin du bloc d'utilisation, fssera forcé de se fermer. Vous donnerez à l'appelant un flux de fichiers FERME (si inutile)!
ToolmakerSteve
4

Voici la solution que vous recherchez

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

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

cela crée une nouvelle autorisation de lecture basée sur la vue pour le chemin de tous les fichiers, puis vérifie si elle est égale à la lecture d'accès au fichier.

DJ Shahar
la source
3

Tout d'abord, ce que Joel Coehoorn a dit.

Aussi: vous devriez examiner les hypothèses qui sous-tendent votre désir d'éviter d'utiliser try / catch à moins que vous n'y soyez obligé. La raison typique pour éviter la logique qui dépend des exceptions (la création d' Exceptionobjets fonctionne mal) n'est probablement pas pertinente pour le code qui ouvre un fichier.

Je suppose que si vous écrivez une méthode qui remplit un List<FileStream>en ouvrant chaque fichier dans un sous-arbre de répertoire et que vous vous attendiez à ce qu'un grand nombre d'entre eux soit inaccessible, vous voudrez peut-être vérifier les autorisations de fichier avant d'essayer d'ouvrir un fichier afin de ne pas obtenir trop d'exceptions. Mais vous gérez toujours l'exception. De plus, il y a probablement quelque chose qui ne va pas dans la conception de votre programme si vous écrivez une méthode qui fait cela.

Robert Rossney
la source
-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
la source
-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
la source
8
-1: utilisez "throw;" pas "throw unauthorizedAccessException;". Vous perdez votre trace de pile.
John Saunders
Pourquoi est attemptspassé par ref? Ça n'a aucun sens. Les tests pour <=au lieu de simplement ==.
Konrad Rudolph
1
@John: eh bien, dans ce cas, il est souhaitable de perdre la trace de pile (profondément imbriquée) de l'appel récursif, donc je pense que dans ce cas, throw exc'est en fait la bonne chose à faire.
Konrad Rudolph
2
@Konrad: @Rudzitis: Je change de raison pour le -1. C'est pire que de visser la pile par "throw ex". Vous visez la pile en induisant artificiellement des niveaux de pile supplémentaires par récursivité à un moment où la profondeur de pile compte réellement. Il s'agit d'un problème itératif et non récursif.
John Saunders