คุณจะตรวจสอบได้อย่างไรว่าการเข้าถึงถูกปฏิเสธสำหรับไฟล์ใน. NET


100

โดยพื้นฐานแล้วฉันต้องการตรวจสอบว่าฉันมีสิทธิ์เปิดไฟล์ก่อนที่จะลองเปิดจริงหรือไม่ ฉันไม่ต้องการใช้ try / catch สำหรับการตรวจสอบนี้เว้นแต่ฉันจะต้องทำ มีคุณสมบัติการเข้าถึงไฟล์ที่ฉันสามารถตรวจสอบก่อนส่งได้หรือไม่?


2
คำบรรยายเมื่อฉันเปลี่ยนแท็ก: "im modified" ไม่ตลก.
Joel Coehoorn

6
ตกลง - ฉันหวังว่าจะมี TryOpen (เช่นรูปแบบการลองแยกวิเคราะห์)
Tristan

คำตอบ:


157

ที่ผ่านมาฉันทำแบบนี้มาแล้วนับครั้งไม่ถ้วนและเกือบทุกครั้งที่ทำไปแล้วฉันคิดผิดที่แม้แต่จะพยายาม

สิทธิ์ของแฟ้ม (แม้การดำรงอยู่ของไฟล์) เป็นสารระเหย - พวกเขาสามารถเปลี่ยนแปลงได้ตลอดเวลา ต้องขอบคุณกฎหมายของ Murphy โดยเฉพาะอย่างยิ่งรวมถึงช่วงเวลาสั้น ๆ ระหว่างเมื่อคุณตรวจสอบไฟล์และเมื่อคุณพยายามเปิด การเปลี่ยนแปลงมีโอกาสมากขึ้นหากคุณอยู่ในพื้นที่ที่คุณรู้ว่าต้องตรวจสอบก่อน มันแปลกมากพอที่จะไม่เกิดขึ้นในสภาพแวดล้อมการทดสอบหรือการพัฒนาของคุณซึ่งมักจะค่อนข้างคงที่ สิ่งนี้ทำให้ปัญหายากต่อการติดตามในภายหลังและทำให้ข้อบกพร่องประเภทนี้นำไปสู่การผลิตได้ง่าย

สิ่งนี้หมายความว่าคุณยังต้องสามารถจัดการกับข้อยกเว้นได้หากการอนุญาตหรือการมีอยู่ของไฟล์ไม่ดีแม้ว่าคุณจะตรวจสอบแล้วก็ตาม จำเป็นต้องมีรหัสการจัดการข้อยกเว้นไม่ว่าคุณจะตรวจสอบสิทธิ์ของไฟล์ล่วงหน้าหรือไม่ รหัสการจัดการข้อยกเว้นจัดเตรียมฟังก์ชันการทำงานทั้งหมดของการมีอยู่หรือการตรวจสอบสิทธิ์ นอกจากนี้ในขณะที่ตัวจัดการข้อยกเว้นเช่นนี้เป็นที่ทราบกันดีว่าช้า แต่สิ่งสำคัญคือต้องจำไว้ว่าดิสก์ i / o นั้นช้ากว่า ... ช้ากว่ามาก ... และการเรียกใช้ฟังก์ชัน. Exists () หรือการตรวจสอบสิทธิ์จะบังคับให้มีการเดินทางเพิ่มเติม ออกจากระบบไฟล์

โดยสรุปแล้วการตรวจสอบเบื้องต้นก่อนที่จะพยายามเปิดไฟล์นั้นทั้งซ้ำซ้อนและสิ้นเปลือง ไม่มีประโยชน์เพิ่มเติมใด ๆ นอกเหนือจากการจัดการข้อยกเว้นมันจะทำร้ายจริงไม่ได้ช่วยประสิทธิภาพของคุณมันเพิ่มต้นทุนในแง่ของโค้ดที่ต้องดูแลมากขึ้นและมันสามารถนำบั๊กที่ละเอียดอ่อนมาสู่โค้ดของคุณได้ ไม่มีข้อเสียใด ๆ เลยในการตรวจสอบเบื้องต้น แต่สิ่งที่ถูกต้องในที่นี้คือเพียงพยายามเปิดไฟล์และใช้ความพยายามของคุณในการจัดการข้อยกเว้นที่ดีหากล้มเหลว เช่นเดียวกันแม้ว่าคุณจะเพิ่งตรวจสอบว่ามีไฟล์อยู่หรือไม่ก็ตาม เหตุผลนี้นำไปใช้ใด ๆทรัพยากรระเหย


5
ตรง นี่คือตัวอย่างคลาสสิกของสภาพการแข่งขัน
Powerlord

3
korro: คุณต้องสามารถจัดการการอนุญาตที่ไม่ดีเมื่อเกิดความล้มเหลวได้และนั่นทำให้การตรวจสอบครั้งแรกซ้ำซ้อนและสิ้นเปลือง
Joel Coehoorn

2
การตรวจสอบเบื้องต้นสามารถช่วยจัดการข้อผิดพลาดทั่วไปได้อย่างสง่างามการมองไปข้างหน้ามักจะง่ายกว่าการจับคู่แอตทริบิวต์ข้อยกเว้นเฉพาะกับสาเหตุเฉพาะ การลอง / จับยังคงเป็นข้อบังคับ
peterchen

5
คำตอบนี้ไม่ได้ตอบคำถาม "วิธีตรวจสอบว่าฉันมีสิทธิ์เปิดไฟล์" ในบางกรณีก่อนที่จะพยายามเปิด ในกรณีนี้อาจเป็นไปได้ว่าหากไม่ได้รับอนุญาตในอินสแตนซ์นั้นซอฟต์แวร์จะไม่พยายามอ่านไฟล์แม้ว่าอาจได้รับอนุญาตหลังจากตรวจสอบสิทธิ์แล้วก็ตาม
Triynko

5
ไม่สำคัญว่าสิทธิ์จะผันผวนหรือไม่เมื่อคุณสนใจเฉพาะสิ่งที่เป็นอยู่ในขณะนั้น ความล้มเหลวควรได้รับการจัดการเสมอ แต่ถ้าคุณตรวจสอบสิทธิ์ในการอ่านและไม่มีอยู่คุณอาจต้องการข้ามการอ่านไฟล์แม้ว่าจะเป็นไปได้ว่าในอีกไม่กี่วินาทีคุณอาจเข้าถึงได้ คุณต้องลากเส้นไว้ที่ไหนสักแห่ง
Triynko

25

เคล็ดลับง่ายๆสำหรับใครก็ตามที่มาที่นี่พร้อมกับปัญหาที่คล้ายกัน:

ระวังแอปการซิงโครไนซ์เว็บเช่น DropBox ฉันเพิ่งใช้เวลา 2 ชั่วโมงในการคิดว่าคำสั่ง "ใช้" (รูปแบบการทิ้ง) ใช้งานไม่ได้ใน. NET

ในที่สุดฉันก็รู้ว่า Dropbox กำลังอ่านและเขียนไฟล์อยู่เบื้องหลังอย่างต่อเนื่องเพื่อที่จะซิงค์ไฟล์เหล่านี้

เดาว่าโฟลเดอร์ Visual Studio Projects ของฉันอยู่ที่ไหน? ภายในโฟลเดอร์ "My Dropbox" แน่นอน

ดังนั้นเมื่อฉันรันแอปพลิเคชันของฉันในโหมด Debug ไฟล์ที่กำลังอ่านและเขียนก็ยังถูกเข้าถึงอย่างต่อเนื่องโดย DropBox เพื่อซิงค์กับเซิร์ฟเวอร์ DropBox สิ่งนี้ทำให้เกิดความขัดแย้งในการล็อก / การเข้าถึง

อย่างน้อยตอนนี้ฉันก็รู้แล้วว่าฉันต้องการฟังก์ชั่น File Open ที่มีประสิทธิภาพมากขึ้น (เช่น TryOpen () ที่จะพยายามหลายครั้ง) ฉันแปลกใจที่มันไม่ได้เป็นส่วนหนึ่งของเฟรมเวิร์กในตัว

[อัปเดต]

นี่คือฟังก์ชั่นตัวช่วยของฉัน:

/// <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;
}

3
@Ash ฉันคิดว่าคุณอ่านคำถามไม่ถูกต้องเขาต้องการหลีกเลี่ยงการลองจับ
Ravisha

10
@ ราวิชาคุณอ่านคำตอบที่ได้รับการโหวตสูงสุดของโจเอลหรือยัง? ขณะที่โจเอลกล่าวว่า"สิ่งที่คุณทำแทนเป็นเพียงการพยายามที่จะเปิดไฟล์และจัดการข้อยกเว้นถ้ามันล้มเหลว" โปรดอย่าลงคะแนนเพียงเพราะคุณไม่ชอบสิ่งที่ไม่สามารถหลีกเลี่ยงได้
เถ้า

ขอบคุณสำหรับรหัส! สิ่งหนึ่งที่ควรใช้โดยใช้เช่นดูคำตอบของ Tazeem ที่นี่
Cel

เมื่อคุณคืนไฟล์สตรีมแล้วผู้โทรusingจะต้องใช้แม้ว่า ...
Cel

@Cel - usingจะไม่ทำงานที่นี่ ในตอนท้ายของบล็อกการใช้งานfsจะบังคับปิด คุณจะให้ผู้โทรสตรีมไฟล์แบบปิด (ไร้ประโยชน์)!
ToolmakerSteve

4

นี่คือทางออกที่คุณกำลังมองหา

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

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

สิ่งนี้จะสร้างสิทธิ์การอ่านใหม่ตามมุมมองสำหรับเส้นทางของไฟล์ทั้งหมดจากนั้นตรวจสอบว่าเท่ากับการเข้าถึงไฟล์หรือไม่


3

ประการแรกสิ่งที่ Joel Coehoorn พูด

นอกจากนี้คุณควรตรวจสอบสมมติฐานที่ว่าคุณต้องการหลีกเลี่ยงการใช้ try / catch น้อยที่สุดเว้นแต่คุณจะต้องทำ เหตุผลทั่วไปในการหลีกเลี่ยงตรรกะที่ขึ้นอยู่กับข้อยกเว้น (การสร้างExceptionวัตถุทำงานได้ไม่ดี) อาจไม่เกี่ยวข้องกับโค้ดที่เปิดไฟล์

ฉันคิดว่าหากคุณกำลังเขียนวิธีการเติมข้อมูลList<FileStream>โดยการเปิดทุกไฟล์ในแผนผังย่อยของไดเรกทอรีและคุณคาดว่าไฟล์จำนวนมากจะไม่สามารถเข้าถึงได้คุณอาจต้องการตรวจสอบสิทธิ์ของไฟล์ก่อนที่จะพยายามเปิดไฟล์เพื่อที่คุณจะไม่ได้ ได้รับข้อยกเว้นมากเกินไป แต่คุณยังคงจัดการกับข้อยกเว้น นอกจากนี้อาจมีบางอย่างผิดปกติอย่างมากกับการออกแบบโปรแกรมของคุณหากคุณกำลังเขียนวิธีการที่ทำเช่นนี้



-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);
        }
    }
}

8
-1: ใช้ "โยน;" ไม่ "โยน unauthorizedAccessException;" คุณกำลังสูญเสียการติดตามสแต็ก
John Saunders

เหตุใดจึงattemptsผ่านการอ้างอิง นั่นไม่สมเหตุสมผล ไม่ไม่การทดสอบแทนเพียง<= ==
Konrad Rudolph

1
@ จอห์น: ในกรณีนี้เป็นที่พึงปรารถนาที่จะสูญเสียการติดตามสแต็ก (ซ้อนกันลึก ๆ ) ของการเรียกแบบเรียกซ้ำดังนั้นฉันคิดว่าในกรณีนี้throw exเป็นสิ่งที่ถูกต้องที่จะทำ
Konrad Rudolph

2
@Konrad: @Rudzitis: ฉันเปลี่ยนเหตุผลของตัวเองเป็น -1 มันแย่กว่าการขันสแต็คโดย "throw ex" คุณกำลังขันสแต็กโดยการกระตุ้นให้เกิดระดับสแต็กพิเศษโดยการเรียกซ้ำในช่วงเวลาที่ความลึกของสแต็กมีความสำคัญ นี่เป็นปัญหาที่เกิดขึ้นซ้ำ ๆ ไม่ใช่ปัญหาที่เกิดซ้ำ
John Saunders
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.