1. Introducción
En Windows (y no solo) cada archivo y directorio tiene un conjunto de propiedades (metadatos): ruta completa, nombre, extensión, tamaño, fechas de creación/modificación/acceso, atributos, etc. Imagínate: un archivo no es solo bytes, también es todo un perfil que se puede leer con FileInfo y DirectoryInfo.
| Propiedad | Descripción | Ejemplo |
|---|---|---|
| Ruta completa | Nombre completo del archivo/carpeta | |
| Nombre | Nombre sin la ruta | |
| Extensión | .txt, .csv, .jpg, etc. | |
| Tamaño (Bytes) | Tamaño del archivo en bytes | |
| Fecha de creación | Cuándo se creó el archivo/carpeta | |
| Fecha de modificación | Cuándo se modificó por última vez el contenido | |
| Atributos | Por ejemplo, solo lectura, oculto, etc. | |
| Carpeta padre | Carpeta en la que está el archivo/directorio | |
2. Trabajo avanzado con propiedades de archivos
Análisis detallado de marcas temporales
Las propiedades CreationTime, LastWriteTime, LastAccessTime devuelven DateTime y su comportamiento depende del sistema de ficheros y las operaciones sobre el archivo.
var fileInfo = new FileInfo("document.txt");
if (fileInfo.Exists)
{
Console.WriteLine($"Creado: {fileInfo.CreationTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"Modificado: {fileInfo.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine($"Accedido: {fileInfo.LastAccessTime:yyyy-MM-dd HH:mm:ss}");
// Diferencia entre creación y última modificación
var age = fileInfo.LastWriteTime - fileInfo.CreationTime;
Console.WriteLine($"El archivo fue modificado durante: {age.TotalDays:F1} días");
}
Es interesante que al copiar un archivo la fecha de creación suele actualizarse a la actual, pero la fecha de modificación puede conservarse del original. Esto es importante para backups y análisis de actividad.
Trabajo con extensiones y nombres de archivos
Las propiedades FullName, Name y Extension parecen simples, pero hay matices: ausencia de extensión, extensiones compuestas como .tar.gz y archivos ocultos que empiezan con punto.
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($"Nombre completo: {file.FullName}");
Console.WriteLine($"Nombre: {file.Name}");
Console.WriteLine($"Extensión: '{file.Extension}'");
// Nombre sin extensión
string nameWithoutExtension = Path.GetFileNameWithoutExtension(file.Name);
Console.WriteLine($"Nombre sin extensión: {nameWithoutExtension}");
Console.WriteLine("---");
}
Tamaño de archivos y formateo
La propiedad Length devuelve el tamaño en bytes; al usuario le resulta más cómodo ver KB/MB/GB. Función auxiliar:
static string FormatFileSize(long bytes)
{
string[] suffixes = { "Б", "КБ", "МБ", "ГБ", "ТБ" };
int counter = 0;
decimal number = bytes;
while (Math.Round(number / 1024) >= 1)
{
number /= 1024;
counter++;
}
return $"{number:N1} {suffixes[counter]}";
}
// Uso
var file = new FileInfo("bigfile.zip");
if (file.Exists)
{
Console.WriteLine($"Tamaño del archivo: {FormatFileSize(file.Length)}");
}
3. Trabajo avanzado con atributos de archivos
Los atributos están representados por la enumeración FileAttributes (un conjunto de flags bit a bit), por eso un archivo puede tener varios atributos a la vez. Es cómodo comprobarlos con HasFlag.
var fileInfo = new FileInfo("important.txt");
Console.WriteLine($"Atributos del archivo: {fileInfo.Attributes}");
// Comprobamos atributos concretos
if (fileInfo.Attributes.HasFlag(FileAttributes.Hidden))
{
Console.WriteLine("¡El archivo está oculto!");
}
if (fileInfo.Attributes.HasFlag(FileAttributes.ReadOnly))
{
Console.WriteLine("¡El archivo está protegido contra escritura!");
}
if (fileInfo.Attributes.HasFlag(FileAttributes.System))
{
Console.WriteLine("¡Este es un archivo del sistema!");
}
Los atributos se pueden modificar programáticamente:
// Hacer el archivo oculto
fileInfo.Attributes |= FileAttributes.Hidden;
// Quitar el atributo "solo lectura"
fileInfo.Attributes &= ~FileAttributes.ReadOnly;
// Establecer varios atributos a la vez
fileInfo.Attributes = FileAttributes.ReadOnly | FileAttributes.Hidden;
4. Trabajo avanzado con directorios
Búsqueda de archivos por patrones
Los métodos GetFiles() y GetDirectories() aceptan patrones y ayudan a filtrar el contenido.
var dir = new DirectoryInfo(@"C:\Projects");
if (dir.Exists)
{
// Encontrar todos los archivos de texto
var textFiles = dir.GetFiles("*.txt");
Console.WriteLine($"Encontrados archivos de texto: {textFiles.Length}");
// Encontrar todos los archivos que empiezan con "temp"
var tempFiles = dir.GetFiles("temp*");
// Encontrar todas las imágenes
var imageExtensions = new[] { "*.jpg", "*.png", "*.gif", "*.bmp" };
var allImages = imageExtensions.SelectMany(ext => dir.GetFiles(ext)).ToArray();
Console.WriteLine($"Encontradas imágenes: {allImages.Length}");
}
Búsqueda recursiva en subcarpetas
Para recorrer todas las subcarpetas usa SearchOption.AllDirectories.
var dir = new DirectoryInfo(@"C:\Development");
// Encontrar todos los archivos C# en todas las subcarpetas
var csharpFiles = dir.GetFiles("*.cs", SearchOption.AllDirectories);
Console.WriteLine($"Total de archivos .cs encontrados: {csharpFiles.Length}");
// Mostrar los primeros 10 archivos con sus rutas
foreach (var file in csharpFiles.Take(10))
{
Console.WriteLine($"{file.FullName} ({FormatFileSize(file.Length)})");
}
Análisis del contenido de un directorio
Ejemplo de análisis resumen: número de archivos/carpetas, tamaño total, distribución por extensiones y top-5 de archivos más grandes.
static void AnalyzeDirectory(DirectoryInfo dir)
{
if (!dir.Exists)
{
Console.WriteLine("¡El directorio no existe!");
return;
}
var files = dir.GetFiles();
var subdirs = dir.GetDirectories();
Console.WriteLine($"Análisis del directorio: {dir.FullName}");
Console.WriteLine($"Archivos: {files.Length}, Subdirectorios: {subdirs.Length}");
if (files.Length == 0)
{
Console.WriteLine("No se encontraron archivos.");
return;
}
long totalSize = files.Sum(f => f.Length);
Console.WriteLine($"Tamaño total de archivos: {FormatFileSize(totalSize)}");
// Agrupación por extensiones
var byExtension = files.GroupBy(f => f.Extension.ToLower())
.OrderByDescending(g => g.Sum(f => f.Length));
Console.WriteLine("\nDistribución por tipos de archivos:");
foreach (var group in byExtension)
{
string ext = string.IsNullOrEmpty(group.Key) ? "(sin extensión)" : group.Key;
long groupSize = group.Sum(f => f.Length);
Console.WriteLine($" {ext}: {group.Count()} archivos, {FormatFileSize(groupSize)}");
}
// Top-5 archivos más grandes
var largestFiles = files.OrderByDescending(f => f.Length).Take(5);
Console.WriteLine("\nArchivos más grandes:");
foreach (var file in largestFiles)
{
Console.WriteLine($" {file.Name}: {FormatFileSize(file.Length)}");
}
}
5. Cálculo optimizado del tamaño de un directorio
Para carpetas grandes en lugar de GetFiles() usa los enumeradores perezosos EnumerateFiles()/EnumerateDirectories(), maneja excepciones y, si quieres, muestra progreso.
static long GetDirectorySizeAdvanced(DirectoryInfo dir, bool showProgress = false)
{
long totalSize = 0;
int fileCount = 0;
var inaccessibleDirs = new List<string>();
try
{
// Usamos EnumerateFiles para directorios grandes (carga perezosa)
foreach (var file in dir.EnumerateFiles())
{
try
{
totalSize += file.Length;
fileCount++;
if (showProgress && fileCount % 1000 == 0)
{
Console.WriteLine($"Procesados archivos: {fileCount}, tamaño: {FormatFileSize(totalSize)}");
}
}
catch (UnauthorizedAccessException)
{
// Archivo inaccesible, saltamos
}
catch (IOException)
{
// Problemas al leer el archivo, saltamos
}
}
// Procesamos recursivamente subdirectorios
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($"Directorios inaccesibles: {inaccessibleDirs.Count}");
}
return totalSize;
}
// Uso
var targetDir = new DirectoryInfo(@"C:\Users");
Console.WriteLine("Empezando cálculo del tamaño del directorio...");
long size = GetDirectorySizeAdvanced(targetDir, showProgress: true);
Console.WriteLine($"Tamaño total: {FormatFileSize(size)}");
6. Aplicaciones prácticas de los metadatos
Búsqueda de archivos por fecha
Buscamos archivos modificados en un periodo dado (útil para limpieza, análisis o auditoría).
static void FindFilesByDate(DirectoryInfo dir, DateTime fromDate, DateTime toDate)
{
Console.WriteLine($"Buscando archivos desde {fromDate:yyyy-MM-dd} hasta {toDate:yyyy-MM-dd}");
var matchingFiles = dir.GetFiles("*", SearchOption.AllDirectories)
.Where(f => f.LastWriteTime >= fromDate && f.LastWriteTime <= toDate)
.OrderByDescending(f => f.LastWriteTime);
Console.WriteLine($"Archivos encontrados: {matchingFiles.Count()}");
foreach (var file in matchingFiles.Take(20))
{
Console.WriteLine($"{file.LastWriteTime:yyyy-MM-dd HH:mm} - {file.Name} ({FormatFileSize(file.Length)})");
}
}
// Ejemplo: encontrar todos los archivos modificados en la última semana
var dir = new DirectoryInfo(@"C:\Documents");
var weekAgo = DateTime.Now.AddDays(-7);
FindFilesByDate(dir, weekAgo, DateTime.Now);
Búsqueda de archivos duplicados
Un método rápido es agrupar por tamaño. Para más precisión puedes añadir comparación de hashes del contenido.
static void FindPotentialDuplicates(DirectoryInfo dir)
{
Console.WriteLine($"Buscando duplicados potenciales en {dir.FullName}");
var files = dir.GetFiles("*", SearchOption.AllDirectories)
.Where(f => f.Length > 0) // Excluimos archivos vacíos
.GroupBy(f => f.Length)
.Where(g => g.Count() > 1) // Solo grupos con múltiples archivos
.OrderByDescending(g => g.Key); // Ordenamos por tamaño
foreach (var sizeGroup in files.Take(10))
{
Console.WriteLine($"\nArchivos de tamaño {FormatFileSize(sizeGroup.Key)} ({sizeGroup.Count()} unidades):");
foreach (var file in sizeGroup)
{
Console.WriteLine($" {file.FullName}");
Console.WriteLine($" Modificado: {file.LastWriteTime:yyyy-MM-dd HH:mm:ss}");
}
}
}
Monitorización de cambios en un directorio
Mostramos archivos modificados en los últimos N minutos.
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($"Archivos modificados en los últimos {minutesBack} minutos:");
if (!recentFiles.Any())
{
Console.WriteLine("No se encontraron cambios.");
return;
}
foreach (var file in recentFiles)
{
var minutesAgo = (DateTime.Now - file.LastWriteTime).TotalMinutes;
Console.WriteLine($"{file.Name} - {minutesAgo:F0} min. atrás ({FormatFileSize(file.Length)})");
}
}
7. Trabajo con directorios padres
La propiedad Directory en FileInfo y Parent en DirectoryInfo permiten subir por la jerarquía.
var file = new FileInfo(@"C:\Projects\MyApp\src\Program.cs");
Console.WriteLine($"Archivo: {file.Name}");
Console.WriteLine($"Carpeta: {file.Directory.Name}");
Console.WriteLine($"Carpeta padre: {file.Directory.Parent.Name}");
Console.WriteLine($"Carpeta raíz del proyecto: {file.Directory.Parent.Parent.Name}");
// Se puede subir por la jerarquía hasta la raíz
var currentDir = file.Directory;
while (currentDir.Parent != null)
{
Console.WriteLine($"Nivel: {currentDir.Name}");
currentDir = currentDir.Parent;
}
Console.WriteLine($"Raíz: {currentDir.Name}");
8. Trampas y errores típicos
1. Caché. Los objetos FileInfo y DirectoryInfo cachean valores. Si el objeto cambió después de crearlo, los datos pueden estar desactualizados. Usa Refresh() para actualizarlos.
var file = new FileInfo("test.txt");
file.Refresh(); // Actualizar metadatos
2. Excepciones de acceso. Algunos archivos y carpetas no son accesibles: maneja UnauthorizedAccessException y otros errores de acceso.
try
{
var files = new DirectoryInfo(path).GetFiles();
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Sin acceso");
}
3. Marcas temporales. Al copiar CreationTime puede cambiar, y LastWriteTime puede conservarse. Esto afecta a reportes y algoritmos de sincronización.
File.Copy("a.txt", "b.txt");
4. Rendimiento. GetFiles() carga todo de una vez y puede ser lento en carpetas grandes. Prefiere EnumerateFiles() para enumeración perezosa.
GO TO FULL VERSION