bagaimana Anda dapat dengan mudah memeriksa apakah akses ditolak untuk file di .NET?

100

Pada dasarnya, saya ingin memeriksa apakah saya memiliki hak untuk membuka file tersebut sebelum saya benar-benar mencoba membukanya; Saya tidak ingin menggunakan coba / tangkap untuk pemeriksaan ini kecuali jika harus. Apakah ada properti akses file yang dapat saya periksa sebelumnya?

Horas
sumber
2
Keterangan saat saya mengubah tag: "im mengoreksi". Tidak bercanda.
Joel Coehoorn
6
Setuju- Saya berharap ada TryOpen (yaitu pola Try-Parse).
Tristan

Jawaban:

157

Saya telah melakukan ini berkali-kali di masa lalu, dan hampir setiap kali saya melakukannya, saya bahkan salah melakukannya.

Izin file (bahkan keberadaan file) tidak stabil - mereka dapat berubah kapan saja. Berkat Hukum Murphy, ini khususnya mencakup periode singkat antara saat Anda memeriksa file dan saat Anda mencoba membukanya. Perubahan bahkan lebih mungkin terjadi jika Anda berada di area yang Anda tahu perlu diperiksa terlebih dahulu. Namun anehnya hal itu tidak akan pernah terjadi di lingkungan pengujian atau pengembangan Anda, yang cenderung cukup statis. Hal ini membuat masalah sulit untuk dilacak nanti dan memudahkan bug semacam ini untuk membuatnya menjadi produksi.

Artinya, Anda masih harus dapat menangani pengecualian jika izin atau keberadaan file buruk, meskipun Anda telah memeriksa. Kode penanganan pengecualian diperlukan , baik Anda memeriksa izin file terlebih dahulu atau tidak. Kode penanganan pengecualian menyediakan semua fungsionalitas pemeriksaan keberadaan atau izin. Selain itu, meskipun penangan pengecualian seperti ini diketahui lambat, penting untuk diingat bahwa i / o disk bahkan lebih lambat ... jauh lebih lambat ... dan memanggil fungsi .Exists () atau memeriksa izin akan memaksa perjalanan tambahan keluar dari sistem file.

Singkatnya, pemeriksaan awal sebelum mencoba membuka file adalah mubazir dan boros. Tidak ada keuntungan tambahan atas penanganan pengecualian, ini sebenarnya akan merugikan, bukan membantu, kinerja Anda, ini menambah biaya dalam hal lebih banyak kode yang harus dipertahankan, dan ini dapat menyebabkan bug halus ke dalam kode Anda. Tidak ada keuntungan sama sekali untuk melakukan pemeriksaan awal. Sebaliknya, hal yang benar di sini adalah mencoba membuka file dan berusaha menjadi penangan pengecualian yang baik jika gagal. Hal yang sama juga berlaku meskipun Anda hanya memeriksa apakah file tersebut ada atau tidak. Alasan ini berlaku untuk setiap sumber daya yang mudah menguap.

Joel Coehoorn
sumber
5
Persis. Ini adalah contoh klasik dari kondisi balapan.
Powerlord
3
korro: Anda harus mampu menangani perizinan yang buruk pada kegagalan, dan itu membuat pemeriksaan awal menjadi mubazir dan boros.
Joel Coehoorn
2
Pemeriksaan awal dapat membantu menangani kesalahan spesifik yang umum dengan baik - melihat ke depan seringkali lebih mudah daripada mencocokkan atribut pengecualian tertentu dengan penyebab tertentu. Coba / tangkap masih tetap wajib.
peterchen
5
Jawaban ini tidak menjawab pertanyaan "bagaimana cara memeriksa apakah saya memiliki hak untuk membuka file" pada suatu saat sebelum mencoba membukanya. Kasusnya mungkin sangat baik, bahwa jika izin tidak diizinkan pada saat itu, perangkat lunak tidak akan mencoba membaca file, bahkan jika izin mungkin diberikan tepat setelah izin diperiksa.
Triynko
5
Tidak masalah apakah perizinan mudah berubah, ketika Anda hanya peduli pada saat itu juga. Kegagalan harus selalu ditangani, tetapi jika Anda memeriksa izin baca dan tidak ada di sana, Anda mungkin ingin melewatkan membaca file, meskipun mungkin sedetik kemudian Anda mungkin memiliki akses. Anda harus menarik garis di suatu tempat.
Triynko
25

Tip cepat untuk siapa pun yang datang ke sini dengan masalah serupa:

Hati-hati dengan aplikasi sinkronisasi web seperti DropBox. Saya hanya menghabiskan 2 jam memikirkan pernyataan "menggunakan" (pola Buang) rusak di NET.

Saya akhirnya menyadari bahwa Dropbox terus membaca dan menulis file di latar belakang, untuk menyinkronkannya.

Tebak di mana folder Visual Studio Projects saya berada? Di dalam folder "Dropbox Saya" tentunya.

Oleh karena itu, saat saya menjalankan aplikasi saya dalam mode Debug, file yang dibaca dan ditulisnya juga terus diakses oleh DropBox untuk disinkronkan dengan server DropBox. Ini menyebabkan konflik penguncian / akses.

Jadi setidaknya sekarang saya tahu bahwa saya membutuhkan fungsi Buka File yang lebih kuat (yaitu TryOpen () yang akan melakukan banyak percobaan). Saya terkejut itu belum menjadi bagian bawaan dari kerangka kerja.

[Memperbarui]

Inilah fungsi pembantu saya:

/// <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;
}
Abu
sumber
3
@ Ash Saya pikir kamu tidak membaca pertanyaan dengan benar DIA ingin menghindari mencoba menangkap.
Ravisha
10
@Ravisha, apakah Anda bahkan membaca jawaban Joel yang paling banyak dipilih? Seperti yang dikatakan Joel, "Yang Anda lakukan hanyalah mencoba membuka file dan menangani pengecualian jika gagal" . Tolong jangan downvote hanya karena Anda tidak menyukai kenyataan bahwa ada sesuatu yang tidak bisa dihindari.
Ash
Terima kasih untuk kodenya! Satu hal, mungkin lebih baik menggunakan menggunakan misalnya lihat jawaban Tazeem di sini
Cel
Mengingat Anda mengembalikan filestream maka usingharus digunakan oleh penelepon meskipun ...
Cel
@ Sel - usingtidak akan berfungsi di sini. Pada akhir blok penggunaan, fsakan ditutup paksa. Anda akan memberikan penelepon sebuah filestream TUTUP (sangat tidak berguna)!
ToolmakerSteve
4

Inilah solusi yang Anda cari

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

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

ini membuat izin baru untuk membaca berdasarkan tampilan untuk jalur semua file lalu memeriksa apakah itu sama dengan akses file baca.

dj shahar
sumber
3

Pertama, apa yang dikatakan Joel Coehoorn.

Juga: Anda harus memeriksa asumsi yang mendasari keinginan Anda untuk menghindari penggunaan coba / tangkap kecuali Anda harus melakukannya. Alasan umum untuk menghindari logika yang bergantung pada pengecualian (membuat Exceptionobjek berkinerja buruk) mungkin tidak relevan dengan kode yang membuka file.

Saya kira jika Anda menulis metode yang mengisi List<FileStream>dengan membuka setiap file dalam subpohon direktori dan Anda mengharapkan sejumlah besar dari mereka tidak dapat diakses, Anda mungkin ingin memeriksa izin file sebelum mencoba membuka file sehingga Anda tidak melakukannya. mendapatkan terlalu banyak pengecualian. Tapi Anda masih akan menangani pengecualian. Juga, mungkin ada sesuatu yang salah dengan desain program Anda jika Anda menulis metode yang melakukan ini.

Robert Rossney
sumber
-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
sumber
-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
sumber
8
-1: gunakan "lempar;" bukan "membuang unauthorizedAccessException;". Anda kehilangan jejak tumpukan Anda.
John Saunders
Mengapa attemptsdisahkan oleh ref? Itu tidak masuk akal. Begitu pula dengan pengujian, <=bukan hanya ==.
Konrad Rudolph
1
@ John: baik, dalam hal ini diinginkan untuk kehilangan jejak tumpukan (sangat bersarang) dari panggilan rekursif jadi saya pikir dalam hal throw exini sebenarnya hal yang benar untuk dilakukan.
Konrad Rudolph
2
@Konrad: @ Rudzitis: Saya mengubah alasan saya untuk -1. Ini lebih buruk daripada mengacaukan tumpukan dengan "membuang mantan". Anda mengacaukan tumpukan dengan mendorong level tumpukan ekstra secara artifisial melalui rekursi pada saat kedalaman tumpukan benar-benar penting. Ini adalah masalah berulang, bukan rekursif.
John Saunders