CodeGym /Cursos /C# SELF /Obtener información sobre archivos y directorios

Obtener información sobre archivos y directorios

C# SELF
Nivel 39 , Lección 2
Disponible

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
C:\data\myfile.txt
Nombre Nombre sin la ruta
myfile.txt
Extensión .txt, .csv, .jpg, etc.
.txt
Tamaño (Bytes) Tamaño del archivo en bytes
4096
Fecha de creación Cuándo se creó el archivo/carpeta
2024-04-16 19:30:10
Fecha de modificación Cuándo se modificó por última vez el contenido
2024-05-01 18:14:02
Atributos Por ejemplo, solo lectura, oculto, etc.
ReadOnly, Hidden
Carpeta padre Carpeta en la que está el archivo/directorio
C:\data

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.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION