1. Introduction
Sous Windows (et pas seulement) chaque fichier et répertoire possède un ensemble de propriétés (métadonnées) : chemin complet, nom, extension, taille, dates de création/modification/accès, attributs, etc. Imaginez : un fichier ce n'est pas que des octets, c'est aussi tout un profil qu'on peut lire avec FileInfo et DirectoryInfo.
| Propriété | Description | Exemple |
|---|---|---|
| Chemin complet | Nom complet du fichier/dossier | |
| Nom | Nom sans le chemin | |
| Extension | .txt, .csv, .jpg, etc. | |
| Taille (Bytes) | Taille du fichier en octets | |
| Date de création | Quand le fichier/dossier a été créé | |
| Date de modification | Quand le contenu a été modifié pour la dernière fois | |
| Attributs | Par exemple ReadOnly, Hidden, etc. | |
| Dossier parent | Le dossier qui contient le fichier/répertoire | |
2. Travail approfondi sur les propriétés des fichiers
Analyse détaillée des timestamps
Les propriétés CreationTime, LastWriteTime, LastAccessTime retournent des DateTime et leur comportement dépend du filesystem et des opérations effectuées sur le fichier.
var fileInfo = new FileInfo("document.txt");
if (fileInfo.Exists)
{
Console.WriteLine($"Créé : {fileInfo.CreationTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"Modifié : {fileInfo.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"Accédé : {fileInfo.LastAccessTime:yyyy-MM-dd HH:mm:ss}");
// Différence entre creation et dernier changement
var age = fileInfo.LastWriteTime - fileInfo.CreationTime;
Console.WriteLine($"Le fichier a été modifié pendant environ : {age.TotalDays:F1} jours");
}
Intéressant : lors d'une copie la date de création est souvent mise à jour à la date courante, tandis que la date de modification peut être conservée depuis l'original. C'est important pour les sauvegardes et l'analyse d'activité.
Travail avec les extensions et les noms de fichiers
Les propriétés FullName, Name et Extension semblent simples, mais il y a des subtilités : absence d'extension, extensions composées comme .tar.gz et les fichiers cachés qui commencent par un point.
var files = new[]
{
new FileInfo("document.txt"),
new FileInfo("archive.tar.gz"),
new FileInfo("README"),
new FileInfo(".gitignore")
};
foreach (var file in files)
{
Console.WriteLine($"Nom complet : {file.FullName}");
Console.WriteLine($"Nom : {file.Name}");
Console.WriteLine($"Extension : '{file.Extension}'");
// Nom sans extension
string nameWithoutExtension = Path.GetFileNameWithoutExtension(file.Name);
Console.WriteLine($"Nom sans extension : {nameWithoutExtension}");
Console.WriteLine("---");
}
Taille des fichiers et formatage
La propriété Length retourne la taille en octets ; pour l'utilisateur c'est plus pratique en Ko/Mo/Go. Fonction utilitaire :
static string FormatFileSize(long bytes)
{
string[] suffixes = { "o", "Ko", "Mo", "Go", "To" };
int counter = 0;
decimal number = bytes;
while (Math.Round(number / 1024) >= 1)
{
number /= 1024;
counter++;
}
return $"{number:N1} {suffixes[counter]}";
}
// Utilisation
var file = new FileInfo("bigfile.zip");
if (file.Exists)
{
Console.WriteLine($"Taille du fichier : {FormatFileSize(file.Length)}");
}
3. Travail avancé avec les attributs de fichiers
Les attributs sont représentés par l'enum FileAttributes (ensemble de flags bit à bit), donc un fichier peut avoir plusieurs attributs en même temps. On vérifie facilement avec HasFlag.
var fileInfo = new FileInfo("important.txt");
Console.WriteLine($"Attributs du fichier : {fileInfo.Attributes}");
// Vérifie des attributs spécifiques
if (fileInfo.Attributes.HasFlag(FileAttributes.Hidden))
{
Console.WriteLine("Fichier caché !");
}
if (fileInfo.Attributes.HasFlag(FileAttributes.ReadOnly))
{
Console.WriteLine("Fichier en lecture seule !");
}
if (fileInfo.Attributes.HasFlag(FileAttributes.System))
{
Console.WriteLine("C'est un fichier système !");
}
On peut modifier les attributs par code :
// Rendre le fichier caché
fileInfo.Attributes |= FileAttributes.Hidden;
// Retirer l'attribut "ReadOnly"
fileInfo.Attributes &= ~FileAttributes.ReadOnly;
// Définir plusieurs attributs en une fois
fileInfo.Attributes = FileAttributes.ReadOnly | FileAttributes.Hidden;
4. Travail avancé avec les répertoires
Recherche de fichiers par pattern
Les méthodes GetFiles() et GetDirectories() acceptent des patterns et aident à filtrer le contenu.
var dir = new DirectoryInfo(@"C:\Projects");
if (dir.Exists)
{
// Trouver tous les fichiers texte
var textFiles = dir.GetFiles("*.txt");
Console.WriteLine($"Fichiers texte trouvés : {textFiles.Length}");
// Trouver tous les fichiers qui commencent par "temp"
var tempFiles = dir.GetFiles("temp*");
// Trouver tous les fichiers images
var imageExtensions = new[] { "*.jpg", "*.png", "*.gif", "*.bmp" };
var allImages = imageExtensions.SelectMany(ext => dir.GetFiles(ext)).ToArray();
Console.WriteLine($"Images trouvées : {allImages.Length}");
}
Recherche récursive dans les sous-dossiers
Pour parcourir tous les sous-dossiers utilisez SearchOption.AllDirectories.
var dir = new DirectoryInfo(@"C:\Development");
// Trouver tous les fichiers C# dans tous les sous-dossiers
var csharpFiles = dir.GetFiles("*.cs", SearchOption.AllDirectories);
Console.WriteLine($"Total de fichiers .cs trouvés : {csharpFiles.Length}");
// Afficher les 10 premiers fichiers avec leurs chemins
foreach (var file in csharpFiles.Take(10))
{
Console.WriteLine($"{file.FullName} ({FormatFileSize(file.Length)})");
}
Analyse du contenu d'un répertoire
Exemple d'analyse récapitulative : nombre de fichiers/dossiers, taille totale, répartition par extension et top-5 des plus gros fichiers.
static void AnalyzeDirectory(DirectoryInfo dir)
{
if (!dir.Exists)
{
Console.WriteLine("Le dossier n'existe pas !");
return;
}
var files = dir.GetFiles();
var subdirs = dir.GetDirectories();
Console.WriteLine($"Analyse du dossier : {dir.FullName}");
Console.WriteLine($"Fichiers : {files.Length}, Sous-dossiers : {subdirs.Length}");
if (files.Length == 0)
{
Console.WriteLine("Aucun fichier trouvé.");
return;
}
long totalSize = files.Sum(f => f.Length);
Console.WriteLine($"Taille totale des fichiers : {FormatFileSize(totalSize)}");
// Groupement par extension
var byExtension = files.GroupBy(f => f.Extension.ToLower())
.OrderByDescending(g => g.Sum(f => f.Length));
Console.WriteLine("\nRépartition par type de fichier :");
foreach (var group in byExtension)
{
string ext = string.IsNullOrEmpty(group.Key) ? "(sans extension)" : group.Key;
long groupSize = group.Sum(f => f.Length);
Console.WriteLine($" {ext} : {group.Count()} fichiers, {FormatFileSize(groupSize)}");
}
// Top-5 des plus gros fichiers
var largestFiles = files.OrderByDescending(f => f.Length).Take(5);
Console.WriteLine("\nLes plus gros fichiers :");
foreach (var file in largestFiles)
{
Console.WriteLine($" {file.Name} : {FormatFileSize(file.Length)}");
}
}
5. Calcul optimisé de la taille d'un dossier
Pour les gros dossiers, au lieu de GetFiles(), préférez les énumérateurs paresseux EnumerateFiles()/EnumerateDirectories(), gérez les exceptions et affichez éventuellement la progression.
static long GetDirectorySizeAdvanced(DirectoryInfo dir, bool showProgress = false)
{
long totalSize = 0;
int fileCount = 0;
var inaccessibleDirs = new List<string>();
try
{
// On utilise EnumerateFiles pour les gros dossiers (chargement paresseux)
foreach (var file in dir.EnumerateFiles())
{
try
{
totalSize += file.Length;
fileCount++;
if (showProgress && fileCount % 1000 == 0)
{
Console.WriteLine($"Fichiers traités : {fileCount}, taille : {FormatFileSize(totalSize)}");
}
}
catch (UnauthorizedAccessException)
{
// Fichier inaccessible, on le saute
}
catch (IOException)
{
// Problème de lecture, on le saute
}
}
// On traite récursivement les sous-dossiers
foreach (var subdir in dir.EnumerateDirectories())
{
try
{
totalSize += GetDirectorySizeAdvanced(subdir, showProgress);
}
catch (UnauthorizedAccessException)
{
inaccessibleDirs.Add(subdir.FullName);
}
}
}
catch (UnauthorizedAccessException)
{
inaccessibleDirs.Add(dir.FullName);
}
if (inaccessibleDirs.Any() && showProgress)
{
Console.WriteLine($"Dossiers inaccessibles : {inaccessibleDirs.Count}");
}
return totalSize;
}
// Utilisation
var targetDir = new DirectoryInfo(@"C:\Users");
Console.WriteLine("On commence le calcul de la taille du dossier...");
long size = GetDirectorySizeAdvanced(targetDir, showProgress: true);
Console.WriteLine($"Taille totale : {FormatFileSize(size)}");
6. Applications pratiques des métadonnées
Recherche de fichiers par date
On recherche les fichiers modifiés dans une période donnée (pratique pour le nettoyage, l'analyse ou l'audit).
static void FindFilesByDate(DirectoryInfo dir, DateTime fromDate, DateTime toDate)
{
Console.WriteLine($"Recherche des fichiers du {fromDate:yyyy-MM-dd} au {toDate:yyyy-MM-dd}");
var matchingFiles = dir.GetFiles("*", SearchOption.AllDirectories)
.Where(f => f.LastWriteTime >= fromDate && f.LastWriteTime <= toDate)
.OrderByDescending(f => f.LastWriteTime);
Console.WriteLine($"Fichiers trouvés : {matchingFiles.Count()}");
foreach (var file in matchingFiles.Take(20))
{
Console.WriteLine($"{file.LastWriteTime:yyyy-MM-dd HH:mm} - {file.Name} ({FormatFileSize(file.Length)})");
}
}
// Exemple : trouver tous les fichiers modifiés la semaine dernière
var dir = new DirectoryInfo(@"C:\Documents");
var weekAgo = DateTime.Now.AddDays(-7);
FindFilesByDate(dir, weekAgo, DateTime.Now);
Recherche de fichiers dupliqués
Méthode rapide — grouper par taille. Pour être sûr, ajouter le calcul d'un hash du contenu.
static void FindPotentialDuplicates(DirectoryInfo dir)
{
Console.WriteLine($"Recherche de doublons potentiels dans {dir.FullName}");
var files = dir.GetFiles("*", SearchOption.AllDirectories)
.Where(f => f.Length > 0) // On exclut les fichiers vides
.GroupBy(f => f.Length)
.Where(g => g.Count() > 1) // Seulement les groupes avec plusieurs fichiers
.OrderByDescending(g => g.Key); // Tri par taille
foreach (var sizeGroup in files.Take(10))
{
Console.WriteLine($"\nFichiers de taille {FormatFileSize(sizeGroup.Key)} ({sizeGroup.Count()} fichiers) :");
foreach (var file in sizeGroup)
{
Console.WriteLine($" {file.FullName}");
Console.WriteLine($" Modifié : {file.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
}
}
}
Surveillance des changements dans un dossier
On affiche les fichiers modifiés depuis les N dernières minutes.
static void MonitorRecentChanges(DirectoryInfo dir, int minutesBack = 60)
{
var cutoffTime = DateTime.Now.AddMinutes(-minutesBack);
var recentFiles = dir.GetFiles("*", SearchOption.AllDirectories)
.Where(f => f.LastWriteTime > cutoffTime)
.OrderByDescending(f => f.LastWriteTime);
Console.WriteLine($"Fichiers modifiés au cours des {minutesBack} dernières minutes :");
if (!recentFiles.Any())
{
Console.WriteLine("Aucun changement trouvé.");
return;
}
foreach (var file in recentFiles)
{
var minutesAgo = (DateTime.Now - file.LastWriteTime).TotalMinutes;
Console.WriteLine($"{file.Name} - il y a {minutesAgo:F0} min. ({FormatFileSize(file.Length)})");
}
}
7. Travail avec les répertoires parents
La propriété Directory sur FileInfo et Parent sur DirectoryInfo permettent de remonter dans la hiérarchie.
var file = new FileInfo(@"C:\Projects\MyApp\src\Program.cs");
Console.WriteLine($"Fichier : {file.Name}");
Console.WriteLine($"Dossier : {file.Directory.Name}");
Console.WriteLine($"Dossier parent : {file.Directory.Parent.Name}");
Console.WriteLine($"Dossier racine du projet : {file.Directory.Parent.Parent.Name}");
// On peut remonter jusqu'à la racine
var currentDir = file.Directory;
while (currentDir.Parent != null)
{
Console.WriteLine($"Niveau : {currentDir.Name}");
currentDir = currentDir.Parent;
}
Console.WriteLine($"Racine : {currentDir.Name}");
8. Pièges et erreurs typiques
1. Mise en cache. Les objets FileInfo et DirectoryInfo mettent en cache les valeurs. Si l'objet a été modifié après sa création, les données peuvent être périmées. Utilisez Refresh() pour actualiser.
var file = new FileInfo("test.txt");
file.Refresh(); // Actualise les métadonnées
2. Exceptions d'accès. Certains fichiers et dossiers sont inaccessibles : gérez UnauthorizedAccessException et d'autres erreurs d'accès.
try
{
var files = new DirectoryInfo(path).GetFiles();
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Accès refusé");
}
3. Timestamps. Lors d'une copie CreationTime peut changer, alors que LastWriteTime peut rester. Ça impacte les rapports et les algos de synchronisation.
File.Copy("a.txt", "b.txt");
4. Performance. GetFiles() charge tout en mémoire et peut ralentir sur les gros dossiers. Préférez EnumerateFiles() pour une énumération paresseuse.
GO TO FULL VERSION