كيف يمكنني حذف دليل يحتوي على ملفات للقراءة فقط في C#؟

StackOverflow https://stackoverflow.com/questions/611921

سؤال

أحتاج إلى حذف دليل يحتوي على ملفات للقراءة فقط.أي النهج هو الأفضل:

  • استخدام DirectoryInfo.Delete(), ، أو،

  • ManagementObject.InvokeMethod("Delete")?

مع DirectoryInfo.Delete(), ، لا بد لي من إيقاف تشغيل سمة القراءة فقط لكل ملف يدويًا، ولكن ManagementObject.InvokeMethod("Delete") لا يبدو أن هناك حاجة لذلك.هل هناك أي موقف يكون فيه أحدهما أفضل من الآخر؟

نموذج التعليمات البرمجية (test.txt للقراءة فقط).

الطريقة الأولى:

DirectoryInfo dir = new DirectoryInfo(@"C:\Users\David\Desktop\");
dir.CreateSubdirectory("Test");

DirectoryInfo test = new DirectoryInfo(@"C:\Users\David\Desktop\Test\");
File.Copy(@"C:\Users\David\Desktop\test.txt", @"C:\Users\David\Desktop\Test\test.txt");
File.SetAttributes(@"C:\Users\David\Desktop\Test\test.txt", FileAttributes.Archive);
test.Delete(true);

الطريقة الثانية:

DirectoryInfo dir = new DirectoryInfo(@"C:\Users\David\Desktop\");
dir.CreateSubdirectory("Test");

DirectoryInfo test = new DirectoryInfo(@"C:\Users\David\Desktop\Test\");
File.Copy(@"C:\Users\David\Desktop\test.txt", @"C:\Users\David\Desktop\Test\test.txt");

string folder = @"C:\Users\David\Desktop\Test";
string dirObject = "Win32_Directory.Name='" + folder + "'";
using (ManagementObject managementObject = new ManagementObject(dirObject))
{
    managementObject.Get();
    ManagementBaseObject outParams = managementObject.InvokeMethod("Delete", null,
    null);
    // ReturnValue should be 0, else failure
    if (Convert.ToInt32(outParams.Properties["ReturnValue"].Value) != 0)
    {
    }
}
هل كانت مفيدة؟

المحلول

إليك طريقة التمديد التي تحدد Attributes ل Normal بشكل متكرر، ثم يحذف العناصر:

public static void DeleteReadOnly(this FileSystemInfo fileSystemInfo)
{
    var directoryInfo = fileSystemInfo as DirectoryInfo;    
    if (directoryInfo != null)
    {
        foreach (FileSystemInfo childInfo in directoryInfo.GetFileSystemInfos())
        {
            childInfo.DeleteReadOnly();
        }
    }

    fileSystemInfo.Attributes = FileAttributes.Normal;
    fileSystemInfo.Delete();
}

نصائح أخرى

إن أبسط طريقة لتجنب المكالمات المتكررة هي استخدام AllDirectories الخيار عند الحصول على FileSystemInfoس، هكذا:

public static void ForceDeleteDirectory(string path) 
{
    var directory = new DirectoryInfo(path) { Attributes = FileAttributes.Normal };

    foreach (var info in directory.GetFileSystemInfos("*", SearchOption.AllDirectories))
    {
        info.Attributes = FileAttributes.Normal;
    }

    directory.Delete(true);
}

جرب هذا،

private void DeleteRecursiveFolder(string pFolderPath)
{
    foreach (string Folder in Directory.GetDirectories(pFolderPath))
    {
        DeleteRecursiveFolder(Folder);
    }

    foreach (string file in Directory.GetFiles(pFolderPath))
    {
        var pPath = Path.Combine(pFolderPath, file);
        FileInfo fi = new FileInfo(pPath);
        File.SetAttributes(pPath, FileAttributes.Normal);
        File.Delete(file);
    }

    Directory.Delete(pFolderPath);
}

طريقة أخرى دون الحاجة إلى التكرار.

public static void ForceDeleteDirectory(string path)
{
    DirectoryInfo root;
    Stack<DirectoryInfo> fols;
    DirectoryInfo fol;
    fols = new Stack<DirectoryInfo>();
    root = new DirectoryInfo(path);
    fols.Push(root);
    while (fols.Count > 0)
    {
        fol = fols.Pop();
        fol.Attributes = fol.Attributes & ~(FileAttributes.Archive | FileAttributes.ReadOnly | FileAttributes.Hidden);
        foreach (DirectoryInfo d in fol.GetDirectories())
        {
            fols.Push(d);
        }
        foreach (FileInfo f in fol.GetFiles())
        {
            f.Attributes = f.Attributes & ~(FileAttributes.Archive | FileAttributes.ReadOnly | FileAttributes.Hidden);
            f.Delete();
        }
    }
    root.Delete(true);
}
private void DeleteRecursiveFolder(DirectoryInfo dirInfo)
{
    foreach (var subDir in dirInfo.GetDirectories())
    {
        DeleteRecursiveFolder(subDir);
    }

    foreach (var file in dirInfo.GetFiles())
    {
        file.Attributes=FileAttributes.Normal;
        file.Delete();
    }

    dirInfo.Delete();
}

الحل الأفضل هو وضع علامة على كافة الملفات كغير قابلة للقراءة فقط، ثم حذف الدليل.

// delete/clear hidden attribute
File.SetAttributes(filePath, File.GetAttributes(filePath) & ~FileAttributes.Hidden);

// delete/clear archive and read only attributes
File.SetAttributes(filePath, File.GetAttributes(filePath) 
    & ~(FileAttributes.Archive | FileAttributes.ReadOnly));

لاحظ أن ~ هو عامل منطقي Bitwise يقوم بإرجاع تكملة القيمة الثنائية المحددة.لم أختبر هذا، ولكن يجب أن تعمل.

شكرًا!

أود أن أقول إن نهجك الأول يبدو أكثر وضوحًا وقابلية للقراءة.الطريقة الثانية لها رائحة انعكاسية، وليست آمنة للكتابة وتبدو غريبة.ال ManagementObject يمكن أن تمثل أشياء متعددة، لذلك ليس من الواضح ذلك .InvokeMethod("Delete") في الواقع يحذف الدليل.

الشيء الذي لا يعجبني في الطريقة الأولى (directory.delete) هو الحالة التي توجد فيها أدلة فرعية تحتوي أيضًا على ملفات للقراءة فقط، ولديها أدلة فرعية تحتوي على ملفات للقراءة فقط أيضًا، وهكذا.يبدو أنه سيتعين عليك إيقاف تشغيل هذه العلامة لكل ملف في الدليل وجميع الدلائل الفرعية بشكل متكرر.

باستخدام الطريقة الثانية، يمكنك فقط حذف الدليل الأول، ولا يتحقق ما إذا كانت الملفات للقراءة فقط.ومع ذلك، هذه هي المرة الأولى التي أستخدم فيها WMI في لغة C#، لذا فأنا لست مرتاحًا لها.لذلك لست متأكدًا من الوقت المناسب لاستخدام أسلوب WMI في التطبيقات الأخرى، بدلاً من استخدام أساليب System.IO فقط.

ظاهريًا، يبدو استخدام أسلوب WMI أكثر كفاءة من التكرار على نظام الملفات بأكمله (افترض على سبيل المثال أن الدليل يحتوي على عشرات الآلاف من الملفات).لكنني لا أعلم أن WMI أيضًا لا يقوم بالتكرار.إذا حدث ذلك، فإن كونك أقرب إلى المعدن (مرة أخرى، الافتراضات) يجب أن يكون أكثر كفاءة.

من أجل الأناقة، أعترف أن الطريقة العودية رائعة.

يجب أن يجيب اختبار الأداء على سؤال الكفاءة.ويمكن لأي منهما أن يكون أنيقًا إذا تم تغليفه بطريقة ملحقة لـ DirectoryInfo.

إليك حل آخر يتجنب التكرار على نفسه.

public static void DirectoryDeleteAll(string directoryPath)
{
    var rootInfo = new DirectoryInfo(directoryPath) { Attributes = FileAttributes.Normal };
    foreach (var fileInfo in rootInfo.GetFileSystemInfos()) fileInfo.Attributes = FileAttributes.Normal;
    foreach (var subDirectory in Directory.GetDirectories(directoryPath, "*", SearchOption.AllDirectories))
    {
        var subInfo = new DirectoryInfo(subDirectory) { Attributes = FileAttributes.Normal };
        foreach (var fileInfo in subInfo.GetFileSystemInfos()) fileInfo.Attributes = FileAttributes.Normal;
    }
    Directory.Delete(directoryPath, true);
}

يعمل هذا عن طريق إعادة تعيين السمات على المجلدات والملفات قبل الحذف، لذا يمكنك فقط إزالة السطر الأخير لأسلوب "DirectoryResetAttributes" واستخدام الحذف بشكل منفصل.

في ملاحظة ذات صلة، أثناء نجاح ذلك، واجهت مشكلات في حذف المسارات التي كانت "طويلة جدًا" وانتهى الأمر باستخدام حل robocopy المنشور هنا: C# حذف مجلد يحتوي على مسارات طويلة

لمتابعة حل Vitaliy Ulantikov، قمت باستكماله بطريقة إعادة تسمية/نقل المجلد:

  public static void renameFolder(String sourcePath, String targetPath) {
     try
     {
        if (System.IO.Directory.Exists(targetPath))
           DeleteFileSystemInfo(new DirectoryInfo(targetPath));
        System.IO.Directory.Move(sourcePath, targetPath);
     }
     catch (Exception ex)
     {
        Console.WriteLine("renameFolder: " + sourcePath + " " + targetPath + " " + ex.Message);
        throw ex;
     }
  }

  private static void DeleteFileSystemInfo(FileSystemInfo fsi) {
     fsi.Attributes = FileAttributes.Normal;
     var di = fsi as DirectoryInfo;

     if (di != null)
     {
        foreach (var dirInfo in di.GetFileSystemInfos())
        {
           DeleteFileSystemInfo(dirInfo);
        }
     }

     fsi.Delete();
  }
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top