如何在C#中删除包含只读文件的目录?

62
我需要删除一个包含只读文件的目录。哪种方法更好?
  • 使用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)
    {
    }
}
11个回答

105

避免递归调用的最简单方法是在获取FileSystemInfo时利用AllDirectories选项,像这样:

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

4
GetFileSystemInfos方法在.NET 4.0中添加了SearchOption参数。 - Rinn

97

这里有一个扩展方法,它会递归地将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();
}

我遇到了这样的情况,使用这段代码会删除所有文件和目录。然后当进程退出时,会重新出现两个空文件夹。我手动删除它们后,它们就一直消失了。 - Manfred
1
我已经在其他几个类似的问题的评论中添加了这个答案,这些问题使用的是删除 RO 而不是设置为 normal。你的方法更好。 - kayleeFrye_onDeck
我不得不在Delete()操作之前添加这行代码:fileSystemInfo.Refresh()。否则,我会收到一个异常,说目录不为空。 - martijnn2008
请注意,当fileSystemInfo是符号链接或联接点时,我们需要跳过目录逻辑。因此,在调用GetFileSystemInfos()之前,我建议检查fileSystemInfo.Attributes.HasFlag(FileAttributes.ReparsePoint) - kobake

10

试一下这个:

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

1
"FileInfo fi" 是没用的。 - Massimo

6

不需要递归的另一种方法。

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

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

很棒的解决方案,我从你那里借鉴了它并只是在dirInfo.Delete()之前添加了一个检查,以查看该目录本身是否是只读的,如果是,就会抛出异常。谢谢。 - Leo

3
最佳解决方案是将所有文件标记为非只读状态,然后删除该目录。
// 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));

请注意,~是一种按位逻辑运算符,返回给定二进制值的补码。我没有测试过这个,但应该能够正常工作。
谢谢!

1

我认为你的第一种方法更加明确和易读。第二种方法有反射的味道,不是类型安全的,并且看起来很奇怪。 ManagementObject 可以表示多个东西,因此并不明显 .InvokeMethod("Delete") 实际上删除了一个目录。


0

这里有另一种避免递归的解决方案。

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# deleting a folder that has long paths


0

我的解决方案适用于.NET Framework 3.5以及.NET Framework版本4及更高版本:

#region DeleteWithReadOnly
internal static void DeleteWithReadOnly(this DirectoryInfo di)
{
   foreach (FileSystemInfo fsi in di.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))
   {
      fsi.Attributes = FileAttributes.Normal;
   }
   di.Delete(true);
}
#endregion

#region DeleteWithReadOnlyNET3_5
internal static void DeleteWithReadOnlyNET3_5(this DirectoryInfo di)
{
   foreach (FileSystemInfo fsi in di.GetFiles("*", SearchOption.AllDirectories))
   {
      fsi.Attributes = FileAttributes.Normal;
   }
   di.Delete(true);
}
#endregion

使用方法:

DirectoryInfo di = new DirectoryInfo(@"C:\TMP");
di.DeleteWithReadOnly();

0

表面上看,使用WMI方法似乎比迭代整个文件系统更有效(例如假设目录有数万个文件)。但我不知道WMI是否也进行迭代。如果是这样,由于更接近金属(再次假设),它应该更有效。

就优雅而言,我承认递归方法很酷。

性能测试应该回答效率问题。如果包装在DirectoryInfo的扩展方法中,任何一种方法都可以很优雅。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接