Jak łatwo sprawdzić, czy odmowa dostępu do pliku w .NET jest zabroniona?

100

Zasadniczo chciałbym sprawdzić, czy mam uprawnienia do otwarcia pliku, zanim faktycznie spróbuję go otworzyć; Nie chcę używać try / catch do tego sprawdzenia, chyba że muszę. Czy istnieje właściwość dostępu do plików, którą mogę sprawdzić wcześniej?

Horas
źródło
2
Podpis po zmianie tagu: „poprawiam”. Bez żartów.
Joel Coehoorn
6
Zgoda - żałuję, że nie było TryOpen (tj. Wzorca Try-Parse).
Tristan

Odpowiedzi:

157

Robiłem to niezliczoną ilość razy w przeszłości i prawie za każdym razem, gdy to robiłem, myliłem się nawet podejmując taką próbę.

Uprawnienia do plików (nawet istnienie pliku) są ulotne - mogą ulec zmianie w dowolnym momencie. Dzięki prawu Murphy'ego obejmuje to w szczególności krótki okres między sprawdzeniem pliku a próbą jego otwarcia. Zmiana jest jeszcze bardziej prawdopodobna, jeśli znajdujesz się w obszarze, o którym wiesz, że musisz najpierw sprawdzić. Jednak, co dziwne, nigdy nie wydarzy się to w twoich środowiskach testowych lub programistycznych, które są dość statyczne. To sprawia, że ​​problem jest trudny do wyśledzenia później i ułatwia wprowadzenie tego rodzaju błędu do produkcji.

Oznacza to, że nadal musisz być w stanie obsłużyć wyjątek, jeśli uprawnienia do pliku lub istnienie są złe, pomimo twojego sprawdzenia. Kod obsługi wyjątków jest wymagany , niezależnie od tego, czy wcześniej sprawdzasz uprawnienia do pliku. Kod obsługi wyjątków zapewnia wszystkie funkcje sprawdzania istnienia lub uprawnień. Ponadto, chociaż wiadomo, że programy obsługi wyjątków, takie jak ta, są wolne, ważne jest, aby pamiętać, że operacje wejścia / wyjścia dysku są jeszcze wolniejsze ... dużo wolniejsze ... a wywołanie funkcji .Exists () lub sprawdzenie uprawnień wymusi dodatkowe wyłączenie z systemu plików.

Podsumowując, wstępne sprawdzenie przed próbą otwarcia pliku jest zarówno zbędne, jak i marnotrawne. Nie ma żadnych dodatkowych korzyści w porównaniu z obsługą wyjątków, w rzeczywistości zaszkodzi, a nie pomoże, wydajności, zwiększa koszty w postaci większej ilości kodu, który musi być utrzymywany, i może wprowadzić subtelne błędy do kodu. Wstępna kontrola nie ma po prostu żadnych korzyści. Zamiast tego, poprawną rzeczą jest po prostu spróbować otworzyć plik i włożyć wysiłek w dobry program obsługi wyjątków, jeśli to się nie powiedzie. To samo jest prawdą, nawet jeśli tylko sprawdzasz, czy plik istnieje. To rozumowanie ma zastosowanie do każdego niestabilnego zasobu.

Joel Coehoorn
źródło
5
Dokładnie. To klasyczny przykład sytuacji wyścigu.
Powerlord
3
korro: i tak musisz umieć sobie radzić ze złymi uprawnieniami w przypadku niepowodzenia, co sprawia, że ​​wstępna kontrola jest zbędna i marnotrawna.
Joel Coehoorn
2
Wstępne sprawdzenie może pomóc z wdzięcznością poradzić sobie z typowymi, określonymi błędami - patrzenie w przyszłość jest często łatwiejsze niż dopasowywanie określonych atrybutów wyjątków do określonych przyczyn. Try / catch nadal pozostaje obowiązkowy.
peterchen
5
Ta odpowiedź nie odpowiada na pytanie „jak sprawdzić, czy mam prawa do otwierania pliku” w pewnym momencie przed próbą otwarcia. Może się zdarzyć, że jeśli zezwolenie nie jest dozwolone w tym przypadku, oprogramowanie nie będzie próbowało odczytać pliku, nawet jeśli zezwolenie może zostać udzielone zaraz po sprawdzeniu uprawnień.
Triynko
5
Nie ma znaczenia, czy uprawnienia są niestabilne, kiedy zależy Ci tylko na tym, jakie są w danej chwili. Niepowodzenie powinno być zawsze obsługiwane, ale jeśli sprawdzisz uprawnienia do odczytu, a go tam nie ma, możesz pominąć czytanie pliku, nawet jeśli jest możliwe, że sekundę później możesz mieć dostęp. Musisz gdzieś wyznaczyć granicę.
Triynko
25

Szybka wskazówka dla każdego, kto przyjeżdża tutaj z podobnym problemem:

Uważaj na aplikacje do synchronizacji sieci, takie jak DropBox. Właśnie spędziłem 2 godziny na myśleniu, że instrukcja „using” (wzorzec usuwania) jest zepsuta w .NET.

W końcu zdałem sobie sprawę, że Dropbox nieustannie odczytuje i zapisuje pliki w tle, aby je zsynchronizować.

Zgadnij, gdzie znajduje się mój folder projektów programu Visual Studio? Oczywiście w folderze „My Dropbox”.

Dlatego, gdy uruchomiłem moją aplikację w trybie debugowania, pliki, które czytała i zapisywały, były również stale uzyskiwane przez DropBox w celu synchronizacji z serwerem DropBox. To spowodowało konflikty blokowania / dostępu.

Więc przynajmniej teraz wiem, że potrzebuję bardziej niezawodnej funkcji File Open (tj. TryOpen (), która wykona wiele prób). Dziwię się, że nie jest to już wbudowana część frameworka.

[Aktualizacja]

Oto moja funkcja pomocnicza:

/// <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;
}
Popiół
źródło
3
@Ash, myślę, że nie przeczytałeś poprawnie pytania, ON chce uniknąć próby złapania.
Ravisha
10
@Ravisha, czy przeczytałeś nawet najpopularniejszą odpowiedź Joela? Jak mówi Joel: „Zamiast tego po prostu spróbuj otworzyć plik i obsłużyć wyjątek, jeśli się nie powiedzie” . Nie głosuj negatywnie tylko dlatego, że nie podoba ci się fakt, że czegoś nie można uniknąć.
Ash
Dzięki za kod! Jedna rzecz, może lepiej użyć używając np. Zobacz odpowiedź Tazeem tutaj
Cel
Biorąc pod uwagę, że zwrócisz strumień plików, usingbędzie musiał być użyty przez dzwoniącego ...
Cel
@Cel - usingtutaj nie zadziała. Pod koniec używania blok fszostanie zmuszony do zamknięcia. Dasz dzwoniącemu ZAMKNIĘTY (tak bezużyteczny) strumień plików!
ToolmakerSteve
4

Oto rozwiązanie, którego szukasz

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

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

tworzy to nowe uprawnienie do odczytu na podstawie widoku ścieżki do wszystkich plików, a następnie sprawdza, czy jest równe odczytowi dostępu do pliku.

dj shahar
źródło
3

Najpierw to, co powiedział Joel Coehoorn.

Ponadto: powinieneś przeanalizować założenia, które leżą u podstaw twojego pragnienia unikania używania try / catch, chyba że musisz. Typowy powód unikania logiki zależnej od wyjątków (tworzenie Exceptionobiektów działa słabo) prawdopodobnie nie ma zastosowania do kodu, który otwiera plik.

Przypuszczam, że jeśli piszesz metodę, która zapełnia a List<FileStream>, otwierając każdy plik w poddrzewie katalogu i spodziewasz się, że duża ich liczba będzie niedostępna, możesz chcieć sprawdzić uprawnienia do pliku przed próbą otwarcia pliku, aby nie uzyskać zbyt wiele wyjątków. Ale nadal poradzisz sobie z wyjątkiem. Ponadto prawdopodobnie jest coś strasznie nie tak z projektem twojego programu, jeśli piszesz metodę, która to robi.

Robert Rossney
źródło
-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
źródło
-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
źródło
8
-1: użyj "rzut;" not "throw unauthorizedAccessException;". Tracisz ślad stosu.
John Saunders,
Dlaczego jest attemptspomijany przez ref? To nie ma sensu. Ani też testowanie <=zamiast tylko ==.
Konrad Rudolph
1
@John: cóż, w tym przypadku pożądane jest , aby stracić (głęboko zagnieżdżony) ślad stosu wywołania rekurencyjnego, więc myślę, że w tym przypadku throw exjest to właściwe rozwiązanie .
Konrad Rudolph
2
@Konrad: @Rudzitis: Zmieniam powód dla -1. To gorsze niż skręcanie stosu przez „rzut ex”. Podkręcasz stos, sztucznie wywołując dodatkowe poziomy stosu poprzez rekurencję w czasie, gdy głębokość stosu ma znaczenie. Jest to problem iteracyjny, a nie rekurencyjny.
John Saunders