J'ai besoin de supprimer un répertoire contenant des fichiers en lecture seule. Quelle approche est la meilleure:
Utilisation de DirectoryInfo.Delete()
, ou,
ManagementObject.InvokeMethod("Delete")
?
Avec DirectoryInfo.Delete()
, je dois désactiver manuellement l'attribut en lecture seule pour chaque fichier, mais ManagementObject.InvokeMethod("Delete")
ne semble pas en avoir besoin. Y a-t-il une situation où l'un est plus préférable à l'autre?
Exemple de code (test.txt est en lecture seule).
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)
{
}
}
Voici une méthode d'extension qui définit Attributes
sur Normal
récursivement, puis supprime les éléments:
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();
}
Le moyen le plus simple d'éviter les appels récursifs est d'utiliser l'option AllDirectories
lors de l'obtention des FileSystemInfo
s, comme ceci:
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);
}
Essaye ça,
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);
}
Une autre méthode sans besoin de récursivité.
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();
}
La meilleure solution consiste à marquer tous les fichiers comme étant en lecture seule, puis à supprimer le répertoire.
// 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));
Notez que ~ est un opérateur logique au niveau du bit qui renvoie le complément de la valeur binaire donnée. Je n'ai pas testé cela, mais cela devrait fonctionner.
Merci!
La chose que je n'aime pas dans la première approche (directory.delete) est le cas où il y a des sous-répertoires qui contiennent également des fichiers en lecture seule, et ils ont des sous-répertoires qui ont également des fichiers en lecture seule, et ainsi de suite. Il semble que vous deviez désactiver cet indicateur pour chaque fichier du répertoire et tous les sous-répertoires de manière récursive.
Avec la deuxième approche, vous pouvez simplement supprimer ce premier répertoire et il ne vérifie pas si les fichiers sont en lecture seule. Cependant, c'est la première fois que j'utilise WMI en C #, donc je ne suis pas du tout à l'aise avec ça. Je ne sais donc pas quand utiliser l'approche WMI pour d'autres applications, au lieu d'utiliser simplement les méthodes System.IO.
Je dirais que votre première approche semble plus explicite et lisible. La deuxième méthode sent la réflexion, n'est pas sécurisée et a l'air bizarre. ManagementObject
peut représenter plusieurs choses, il n'est donc pas évident que .InvokeMethod("Delete")
supprime réellement un répertoire.
En surface, l'utilisation de l'approche WMI semble plus efficace que l'itération sur l'ensemble du système de fichiers (supposons, par exemple, que le répertoire contient des dizaines de milliers de fichiers). Mais je ne sais pas que WMI ne fait pas non plus d'itérations. Si c'est le cas, étant plus proche du métal (encore une fois, des hypothèses), il devrait être plus efficace.
Pour l'élégance, je concède que la méthode récursive est cool.
Les tests de performance devraient répondre à la question de l'efficacité. Et l'un ou l'autre peut être élégant s'il est enveloppé dans une méthode d'extension de DirectoryInfo.
Pour suivre la solution de Vitaliy Ulantikov, je l'ai complétée par une méthode de changement de nom/dossier:
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();
}
Voici une autre solution qui évite la récursivité sur elle-même.
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);
}
Cela fonctionne en réinitialisant les attributs des dossiers et des fichiers avant la suppression, vous pouvez donc simplement supprimer la dernière ligne d'une méthode 'DirectoryResetAttributes' et utiliser la suppression séparément.
Sur une note connexe, alors que cela fonctionnait, j'ai ensuite eu des problèmes avec la suppression de chemins qui étaient "trop longs" et j'ai fini par utiliser une solution de robocopy publiée ici: C # suppression d'un dossier qui a de longs chemins