Wie können Sie einfach überprüfen, ob der Zugriff für eine Datei in .NET verweigert wird?

100

Grundsätzlich möchte ich prüfen, ob ich zum Öffnen der Datei berechtigt bin, bevor ich tatsächlich versuche, sie zu öffnen. Ich möchte für diese Prüfung keinen Versuch / Fang verwenden, es sei denn, ich muss. Gibt es eine Dateizugriffseigenschaft, die ich vorher überprüfen kann?

Horas
quelle
2
Beschriftung, als ich das Tag geändert habe: "Ich korrigiere". Kein Witz.
Joel Coehoorn
6
Einverstanden - Ich wünschte, es gäbe einen TryOpen (dh Try-Parse-Muster).
Tristan

Antworten:

157

Ich habe dies in der Vergangenheit unzählige Male getan, und fast jedes Mal, wenn ich es getan habe, war ich falsch, den Versuch überhaupt zu machen.

Dateiberechtigungen (auch das Vorhandensein von Dateien) sind flüchtig - sie können sich jederzeit ändern. Dank des Murphy-Gesetzes umfasst dies insbesondere die kurze Zeit zwischen dem Überprüfen der Datei und dem Versuch, sie zu öffnen. Eine Änderung ist noch wahrscheinlicher, wenn Sie sich in einem Bereich befinden, in dem Sie wissen, dass Sie dies zuerst überprüfen müssen. Seltsamerweise wird es jedoch niemals in Ihren Test- oder Entwicklungsumgebungen passieren, die dazu neigen, ziemlich statisch zu sein. Dies macht es schwierig, das Problem später aufzuspüren, und macht es für diese Art von Fehler einfach, es in die Produktion zu bringen.

Dies bedeutet, dass Sie die Ausnahme trotz Ihrer Überprüfung immer noch behandeln müssen, wenn die Dateiberechtigungen oder das Vorhandensein schlecht sind. Ausnahmebehandlungscode ist erforderlich , unabhängig davon, ob Sie im Voraus nach den Berechtigungen der Datei suchen oder nicht. Der Ausnahmebehandlungscode bietet alle Funktionen der Existenz- oder Berechtigungsprüfung. Obwohl solche Ausnahmebehandlungsroutinen als langsam bekannt sind, ist es wichtig zu beachten, dass die Festplatten- E / A noch langsamer ist ... viel langsamer ... und das Aufrufen der Funktion .Exists () oder das Überprüfen von Berechtigungen eine zusätzliche Auslösung erzwingen aus dem Dateisystem.

Zusammenfassend ist eine erste Überprüfung vor dem Versuch, die Datei zu öffnen, sowohl redundant als auch verschwenderisch. Es gibt keinen zusätzlichen Vorteil gegenüber der Ausnahmebehandlung, es wird Ihre Leistung tatsächlich beeinträchtigen, nicht helfen, es erhöht die Kosten in Bezug auf mehr Code, der gewartet werden muss, und es kann subtile Fehler in Ihren Code einbringen. Es gibt überhaupt keinen Vorteil bei der ersten Überprüfung. Stattdessen sollten Sie hier nur versuchen, die Datei zu öffnen und Ihre Bemühungen in einen guten Ausnahmebehandler zu investieren, wenn dies fehlschlägt. Das Gleiche gilt auch dann, wenn Sie nur prüfen, ob die Datei vorhanden ist oder nicht. Diese Argumentation gilt für jede flüchtige Ressource.

Joel Coehoorn
quelle
5
Genau. Dies ist ein klassisches Beispiel für eine Rennbedingung.
Powerlord
3
korro: Sie müssen ohnehin in der Lage sein, mit schlechten Berechtigungen bei Fehlern umzugehen, und das macht die anfängliche Überprüfung überflüssig und verschwenderisch.
Joel Coehoorn
2
Eine erste Überprüfung kann dazu beitragen, häufig auftretende spezifische Fehler ordnungsgemäß zu behandeln. Ein Blick in die Zukunft ist häufig einfacher, als bestimmte Ausnahmeattribute bestimmten Ursachen zuzuordnen. Der Versuch / Fang bleibt weiterhin obligatorisch.
Peterchen
5
Diese Antwort beantwortet nicht die Frage "Wie kann ich überprüfen, ob ich zum Öffnen der Datei berechtigt bin?", Bevor versucht wird, sie zu öffnen. Der Fall kann sehr gut sein, dass die Software nicht versucht, die Datei zu lesen, wenn in diesem Fall keine Berechtigung zulässig ist, selbst wenn die Berechtigung möglicherweise unmittelbar nach Überprüfung der Berechtigungen erteilt wird.
Triynko
5
Es spielt keine Rolle, ob Berechtigungen flüchtig sind, wenn Sie sich nur darum kümmern, was sie gerade sind. Fehler sollten immer behandelt werden. Wenn Sie jedoch nach einer Leseberechtigung suchen und diese nicht vorhanden ist, können Sie das Lesen der Datei überspringen, auch wenn Sie möglicherweise eine Sekunde später Zugriff haben. Sie müssen irgendwo die Grenze ziehen.
Triynko
25

Schneller Tipp für alle anderen, die mit einem ähnlichen Problem hierher kommen:

Achten Sie auf Web-Synchronisierungs-Apps wie DropBox. Ich habe gerade 2 Stunden damit verbracht zu denken, dass die "using" -Anweisung (Dispose pattern) in .NET fehlerhaft ist.

Schließlich wurde mir klar, dass Dropbox ständig Dateien im Hintergrund liest und schreibt, um sie zu synchronisieren.

Ratet mal, wo sich mein Visual Studio Projects-Ordner befindet? Natürlich im Ordner "My Dropbox".

Während ich meine Anwendung im Debug-Modus ausführte, wurde daher auch von DropBox kontinuierlich auf die Dateien zugegriffen, die sie las und schrieb, um sie mit dem DropBox-Server zu synchronisieren. Dies verursachte die Sperr- / Zugriffskonflikte.

Zumindest weiß ich jetzt, dass ich eine robustere Funktion zum Öffnen von Dateien benötige (dh TryOpen (), die mehrere Versuche unternimmt). Ich bin überrascht, dass es nicht bereits ein integrierter Bestandteil des Frameworks ist.

[Aktualisieren]

Hier ist meine Hilfsfunktion:

/// <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;
}
Asche
quelle
3
@Ash Ich denke du hast die Frage nicht richtig gelesen ER will vermeiden, es zu versuchen.
Ravisha
10
@ Ravisha, hast du überhaupt Joels bestbewertete Antwort gelesen? Als Joel sagt : „Was Sie tun , anstatt nur versuchen , die Datei zu öffnen und die Ausnahme zu behandeln , wenn es ausfällt“ . Bitte stimmen Sie nicht ab, nur weil Ihnen die Tatsache nicht gefällt, dass etwas nicht vermieden werden kann.
Ash
Danke für den Code! Eine Sache, es könnte besser sein, zB Tazeem Antwort hier zu verwenden
Cel
Wenn Sie den Dateistream zurückgeben using, muss der vom Anrufer verwendet werden ...
Cel
@ Cel - usingwird hier nicht funktionieren. Am Ende des Verwendungsblocks fswird das Schließen erzwungen. Sie geben dem Anrufer einen GESCHLOSSENEN (so nutzlosen) Dateistream!
ToolmakerSteve
4

Hier ist die Lösung, die Sie suchen

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

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

Dadurch wird eine neue Leseberechtigung basierend auf der Ansicht für den Pfad aller Dateien erstellt und anschließend überprüft, ob sie dem Lesezugriff auf Dateien entspricht.

DJ Shahar
quelle
3

Zuerst, was Joel Coehoorn gesagt hat.

Außerdem: Sie sollten die Annahmen untersuchen, die Ihrem Wunsch zugrunde liegen, die Verwendung von try / catch zu vermeiden, es sei denn, Sie müssen. Der typische Grund für die Vermeidung von Logik, die von Ausnahmen abhängt (das Erstellen von ExceptionObjekten funktioniert schlecht), ist wahrscheinlich nicht relevant für Code, der eine Datei öffnet.

Ich nehme an, wenn Sie eine Methode schreiben, die a List<FileStream>durch Öffnen jeder Datei in einem Verzeichnis-Teilbaum auffüllt, und Sie erwartet haben, dass auf eine große Anzahl von Dateien nicht zugegriffen werden kann, sollten Sie die Dateiberechtigungen überprüfen, bevor Sie versuchen, eine Datei zu öffnen, damit Sie dies nicht tun bekomme zu viele Ausnahmen. Aber Sie würden die Ausnahme trotzdem behandeln. Außerdem stimmt wahrscheinlich etwas furchtbar nicht mit dem Design Ihres Programms, wenn Sie eine Methode schreiben, die dies tut.

Robert Rossney
quelle
-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
quelle
-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
quelle
8
-1: benutze "throw;" nicht "unauthorizedAccessException auslösen;". Sie verlieren Ihre Stapelverfolgung.
John Saunders
Warum wird attemptsvon ref übergeben? Das macht keinen Sinn. Auch nicht testen <=statt nur ==.
Konrad Rudolph
1
@ John: Nun, in diesem Fall ist es wünschenswert , die (tief verschachtelte) Stapelspur des rekursiven Aufrufs zu verlieren, daher denke ich, dass dies in diesem Fall throw extatsächlich das Richtige ist .
Konrad Rudolph
2
@Konrad: @Rudzitis: Ich ändere meinen Grund für die -1. Es ist schlimmer als den Stapel durch "Ex werfen" zu schrauben. Sie schrauben den Stapel, indem Sie künstlich zusätzliche Stapelebenen durch Rekursion zu einem Zeitpunkt induzieren, an dem die Stapeltiefe tatsächlich wichtig ist. Dies ist ein iteratives Problem, kein rekursives.
John Saunders