1. Introducción
En .NET para trabajar con archivos y carpetas existen dos grupos de clases filosóficamente diferentes:
Clases estáticas: File, Directory — funcionan como un conjunto de funciones utilitarias: llamas al método, pasas la ruta y obtienes el resultado.
Clases de instancia: FileInfo, DirectoryInfo — representan archivos y carpetas concretos como objetos con sus propias propiedades y métodos.
¿Por qué ambos enfoques? Es un compromiso entre simplicidad de uso (métodos estáticos) y la flexibilidad orientada a objetos (clases de instancia). Cada uno es conveniente en sus escenarios.
2. Filosofía de las clases estáticas
Las clases File y Directory siguen el principio "¡Hazlo y ya!". Proporcionan una interfaz directa para ejecutar operaciones contra el sistema de archivos sin necesidad de crear objetos intermedios.
// Comprobar la existencia de un archivo
if (File.Exists("document.txt"))
{
// Leer todo el texto
string content = File.ReadAllText("document.txt");
// Crear una copia de seguridad
File.Copy("document.txt", "document_backup.txt");
// Eliminar el original
File.Delete("document.txt");
}
Esto es cómodo para operaciones simples y puntuales: no hace falta pensar en objetos, su ciclo de vida o estado — simplemente llamas al método con la ruta del archivo.
De forma similar funciona Directory:
// Crear una carpeta
Directory.CreateDirectory(@"C:\MyProject\Data");
// Obtener todos los archivos de texto
string[] textFiles = Directory.GetFiles(@"C:\MyProject", "*.txt");
// Comprobar si existe la carpeta
if (Directory.Exists(@"C:\Temp"))
{
Directory.Delete(@"C:\Temp", recursive: true);
}
3. Comparación de enfoques con ejemplos prácticos
Ejemplo 1: Copiado simple de un archivo
Enfoque estático:
string sourcePath = "original.txt";
string destinationPath = "copy.txt";
if (File.Exists(sourcePath))
{
File.Copy(sourcePath, destinationPath, overwrite: true);
Console.WriteLine("Archivo copiado");
}
else
{
Console.WriteLine("Archivo origen no encontrado");
}
Enfoque de instancia:
var sourceFile = new FileInfo("original.txt");
var destinationFile = new FileInfo("copy.txt");
if (sourceFile.Exists)
{
sourceFile.CopyTo(destinationFile.FullName, overwrite: true);
Console.WriteLine("Archivo copiado");
}
else
{
Console.WriteLine("Archivo origen no encontrado");
}
Aquí el enfoque estático es más conciso. ¿Pero qué pasa si necesitas información adicional?
Ejemplo 2: Copiado con comprobación de tamaño
Enfoque estático:
string sourcePath = "largefile.zip";
string destinationPath = "backup.zip";
if (File.Exists(sourcePath))
{
var fileInfo = new FileInfo(sourcePath); // ¡De todos modos hay que crear un objeto!
if (fileInfo.Length > 100 * 1024 * 1024) // Más de 100 MB
{
Console.WriteLine($"Atención: se está copiando un archivo grande ({fileInfo.Length / 1024 / 1024} MB)");
}
File.Copy(sourcePath, destinationPath, overwrite: true);
}
Enfoque de instancia:
var sourceFile = new FileInfo("largefile.zip");
if (sourceFile.Exists)
{
if (sourceFile.Length > 100 * 1024 * 1024) // Más de 100 MB
{
Console.WriteLine($"Atención: se está copiando un archivo grande ({sourceFile.Length / 1024 / 1024} MB)");
}
sourceFile.CopyTo("backup.zip", overwrite: true);
}
Aquí el enfoque orientado a objetos es más natural: trabajas con el archivo como objeto y usas sus propiedades.
Ejemplo 3: Análisis del contenido de una carpeta
Enfoque estático:
string folderPath = @"C:\Documents";
if (Directory.Exists(folderPath))
{
string[] files = Directory.GetFiles(folderPath);
string[] subdirs = Directory.GetDirectories(folderPath);
Console.WriteLine($"Archivos: {files.Length}, Carpetas: {subdirs.Length}");
// Para obtener tamaños aún necesitas objetos FileInfo
long totalSize = 0;
foreach (string filePath in files)
{
var fileInfo = new FileInfo(filePath);
totalSize += fileInfo.Length;
}
Console.WriteLine($"Tamaño total: {totalSize / 1024} KB");
}
Enfoque de instancia:
var folder = new DirectoryInfo(@"C:\Documents");
if (folder.Exists)
{
var files = folder.GetFiles();
var subdirs = folder.GetDirectories();
Console.WriteLine($"Archivos: {files.Length}, Carpetas: {subdirs.Length}");
long totalSize = files.Sum(f => f.Length); // ¡La información ya está disponible!
Console.WriteLine($"Tamaño total: {totalSize / 1024} KB");
}
Aquí DirectoryInfo gana: las colecciones FileInfo[] ya contienen metadatos.
4. Criterios para elegir el enfoque
Usa las clases estáticas (File/Directory) cuando:
- La operación es simple y puntual. Necesitas comprobar rápidamente la existencia de un archivo, eliminarlo o leer su contenido — los métodos estáticos son ideales.
// Lectura simple de configuración
if (File.Exists("config.json"))
{
string config = File.ReadAllText("config.json");
// procesar la configuración...
}
- No necesitas información de propiedades del archivo. Si el tamaño, las fechas o atributos no importan — las llamadas estáticas son más cortas y encajan mejor.
- Trabajas con rutas en forma de cadenas, no con archivos como objetos. Cuando la lógica opera con rutas string, los métodos estáticos son más naturales.
Usa las clases de instancia (FileInfo/DirectoryInfo) cuando:
- Necesitas mucha información sobre un archivo/carpeta. Propiedades como Length, CreationTime, Attributes están disponibles directamente.
var logFile = new FileInfo("application.log");
Console.WriteLine($"Tamaño del log: {logFile.Length / 1024} KB");
Console.WriteLine($"Última modificación: {logFile.LastWriteTime}");
Console.WriteLine($"Ubicación: {logFile.Directory.FullName}");
- Realizas múltiples operaciones sobre un mismo archivo. Creas el objeto una vez y lo usas para distintas acciones.
var document = new FileInfo("report.docx");
if (document.Exists)
{
var backup = document.CopyTo($"report_backup_{DateTime.Now:yyyyMMdd}.docx");
document.MoveTo("archive/report.docx");
Console.WriteLine($"Documento archivado, creada copia {backup.Name}");
}
- Trabajas con colecciones de archivos. GetFiles() y GetDirectories() devuelven objetos con información completa.
- Necesitas una arquitectura orientada a objetos. Archivos/carpetas como objetos son más fáciles de pasar, almacenar y usar en LINQ.
5. Particularidades de rendimiento y caching
Caching en las clases de instancia
Una de las características clave de FileInfo/DirectoryInfo es el caching de metadatos. En el primer acceso a una propiedad (por ejemplo, Length o CreationTime) .NET realiza una llamada al sistema, carga toda la información y la cachea dentro del objeto.
var file = new FileInfo("document.txt");
// Primer acceso - llamada al sistema para cargar todos los metadatos
long size = file.Length;
// Accesos posteriores usan el caché - muy rápido
DateTime created = file.CreationTime;
DateTime modified = file.LastWriteTime;
bool readOnly = file.IsReadOnly;
Si necesitas varias propiedades de un mismo archivo, el enfoque orientado a objetos es más eficiente: una llamada al sistema en lugar de varias.
Problema del caché desactualizado
La contrapartida es que la información puede quedar desactualizada si el archivo cambia fuera de tu programa. Usa Refresh():
var file = new FileInfo("data.txt");
Console.WriteLine($"Tamaño: {file.Length}"); // Por ejemplo, 1000 bytes
// Mientras tanto otra aplicación modifica el archivo...
Console.WriteLine($"Tamaño: {file.Length}"); // ¡Sigue siendo 1000 bytes por el caché!
// Forzar actualización
file.Refresh();
Console.WriteLine($"Tamaño: {file.Length}"); // Ahora el tamaño real
Las llamadas estáticas siempre van directamente al sistema de archivos, por eso no tienen este problema.
Operaciones masivas
En grandes conjuntos de archivos la elección del API afecta el rendimiento:
// Enfoque estático — muchas llamadas al sistema
string[] files = Directory.GetFiles(@"C:\Photos");
foreach (string filePath in files)
{
var info = new FileInfo(filePath); // Llamada al sistema por cada archivo
if (info.Length > 10 * 1024 * 1024) // Más de 10 MB
{
Console.WriteLine($"Foto grande: {info.Name}");
}
}
// Enfoque de instancia — una llamada al sistema por carpeta
var photosDir = new DirectoryInfo(@"C:\Photos");
foreach (var file in photosDir.GetFiles()) // La información se carga de una vez
{
if (file.Length > 10 * 1024 * 1024)
{
Console.WriteLine($"Foto grande: {file.Name}");
}
}
6. Diferencias en el API y capacidades
Capacidades únicas de las clases de instancia
var file = new FileInfo(@"C:\Projects\MyApp\source\Program.cs");
// Navegación por la jerarquía de carpetas
DirectoryInfo projectDir = file.Directory.Parent; // MyApp
DirectoryInfo sourceDir = file.Directory; // source
// Información detallada del archivo
Console.WriteLine($"Extensión: {file.Extension}");
Console.WriteLine($"Sólo lectura: {file.IsReadOnly}");
Console.WriteLine($"Atributos: {file.Attributes}");
// Trabajar con la carpeta como objeto
var dir = new DirectoryInfo(@"C:\Projects");
DirectoryInfo parent = dir.Parent; // C:\
DirectoryInfo root = dir.Root; // C:\
Capacidades únicas de las clases estáticas
// Leer y escribir texto en una línea
string content = File.ReadAllText("config.txt");
File.WriteAllText("output.txt", "Hello World");
// Trabajar con líneas
string[] lines = File.ReadAllLines("data.txt");
File.WriteAllLines("output.txt", new[] { "Line 1", "Line 2" });
// Añadir texto a un archivo
File.AppendAllText("log.txt", $"{DateTime.Now}: Application started\n");
// Trabajar con bytes
byte[] data = File.ReadAllBytes("image.jpg");
File.WriteAllBytes("copy.jpg", data);
7. Recomendaciones prácticas
Escenario 1: Utilidad de backup
Para copiar con comprobación de tamaños y fechas las clases de instancia son más cómodas:
public void BackupDirectory(string sourcePath, string backupPath)
{
var sourceDir = new DirectoryInfo(sourcePath);
var backupDir = new DirectoryInfo(backupPath);
if (!backupDir.Exists)
backupDir.Create();
foreach (var file in sourceDir.GetFiles())
{
var backupFile = new FileInfo(Path.Combine(backupPath, file.Name));
// Copiamos sólo si el archivo es más reciente o no existe
if (!backupFile.Exists || file.LastWriteTime > backupFile.LastWriteTime)
{
file.CopyTo(backupFile.FullName, overwrite: true);
Console.WriteLine($"Copiado: {file.Name} ({file.Length / 1024} KB)");
}
}
}
Escenario 2: Trabajo simple con archivos de texto
Para cargar/guardar datos simples los métodos estáticos son suficientes:
public void SaveUserPreferences(string username, string theme, bool notifications)
{
string configPath = "user.config";
string[] settings = {
$"Username={username}",
$"Theme={theme}",
$"Notifications={notifications}"
};
File.WriteAllLines(configPath, settings);
}
public Dictionary<string, string> LoadUserPreferences()
{
string configPath = "user.config";
var preferences = new Dictionary<string, string>();
if (!File.Exists(configPath))
{
// Creamos ajustes por defecto
SaveUserPreferences("User", "Light", true);
return LoadUserPreferences();
}
string[] lines = File.ReadAllLines(configPath);
foreach (string line in lines)
{
if (line.Contains('='))
{
string[] parts = line.Split('=', 2);
preferences[parts[0]] = parts[1];
}
}
return preferences;
}
Escenario 3: Análisis del sistema de archivos
Para informes de uso de disco las clases de instancia son ideales:
public void AnalyzeDiskUsage(string path)
{
var directory = new DirectoryInfo(path);
var report = new Dictionary<string, long>();
foreach (var file in directory.GetFiles("*", SearchOption.AllDirectories))
{
string extension = file.Extension.ToLower();
if (string.IsNullOrEmpty(extension))
extension = "(sin extensión)";
if (!report.ContainsKey(extension))
report[extension] = 0;
report[extension] += file.Length;
}
var sortedReport = report.OrderByDescending(kvp => kvp.Value);
foreach (var item in sortedReport.Take(10))
{
Console.WriteLine($"{item.Key}: {item.Value / 1024 / 1024} MB");
}
}
8. Errores comunes y trampas
Error: Mezclar enfoques sin necesidad
A veces se empieza con métodos estáticos y luego se crean objetos para metadatos — resulta en duplicación innecesaria de accesos:
// Ineficiente - dos accesos al sistema de archivos
if (File.Exists("document.txt"))
{
var fileInfo = new FileInfo("document.txt"); // Duplicación de la comprobación de existencia
Console.WriteLine($"Tamaño: {fileInfo.Length}");
}
// Mejor usar directamente el enfoque orientado a objetos
var fileInfo = new FileInfo("document.txt");
if (fileInfo.Exists)
{
Console.WriteLine($"Tamaño: {fileInfo.Length}");
}
Error: Ignorar el caché desactualizado
Al trabajar durante mucho tiempo con archivos que pueden cambiar externamente, actualiza el caché con Refresh():
var logFile = new FileInfo("application.log");
while (true)
{
logFile.Refresh(); // Actualizamos la información del archivo
if (logFile.Length > 100 * 1024 * 1024) // 100 MB
{
// Archivamos el log
logFile.MoveTo($"logs/archived_{DateTime.Now:yyyyMMdd_HHmmss}.log");
break;
}
Thread.Sleep(60000); // Comprobamos cada minuto
}
Error: Elección incorrecta para operaciones masivas
Al procesar miles de archivos la elección del API es crítica para el rendimiento:
// Lento - muchas llamadas pequeñas al sistema de archivos
string[] allFiles = Directory.GetFiles(@"C:\BigFolder", "*", SearchOption.AllDirectories);
var largeFiles = allFiles.Where(path => new FileInfo(path).Length > 1024 * 1024).ToList();
// Más rápido - usamos la información ya preparada
var folder = new DirectoryInfo(@"C:\BigFolder");
var largeFiles = folder.GetFiles("*", SearchOption.AllDirectories)
.Where(file => file.Length > 1024 * 1024)
.ToList();
GO TO FULL VERSION