Serialización y deserialización de objetos
La serialización es el proceso fundamental que permite convertir objetos complejos de nuestra aplicación en un formato que puede ser almacenado, transmitido o procesado por otros sistemas. Este mecanismo resulta esencial para la persistencia de datos, la comunicación entre aplicaciones y el intercambio de información a través de redes. En C#, disponemos de múltiples estrategias de serialización que van desde formatos binarios altamente eficientes hasta representaciones textuales legibles como XML y JSON.
La deserialización complementa este proceso, permitiendo reconstruir objetos a partir de sus representaciones serializadas. Esta capacidad bidireccional nos permite mantener el estado de nuestras aplicaciones, crear sistemas de cache sofisticados y establecer protocolos de comunicación robustos entre diferentes componentes de software.
En este artículo exploraremos las diferentes técnicas de serialización disponibles en .NET, desde los métodos tradicionales hasta las aproximaciones modernas, analizando sus ventajas, limitaciones y casos de uso más apropiados para cada situación.
Conceptos fundamentales de serialización
La serialización es el proceso de convertir un objeto en memoria en una secuencia de bytes o una representación textual que puede ser almacenada o transmitida. La deserialización es el proceso inverso: reconstruir el objeto original a partir de estos datos serializados.
Tipos de serialización en .NET
Tipo de serialización | Formato | Características | Uso recomendado |
---|---|---|---|
Binaria | Binario | Compacta, rápida, específica de .NET | Sistemas internos .NET |
XML | Texto XML | Legible, estándar, multiplataforma | Configuración, intercambio de datos |
JSON | Texto JSON | Ligero, web-friendly, popular | APIs REST, aplicaciones web |
Protocolo personalizado | Binario/Texto | Optimizado para casos específicos | Alta performance, protocolos propios |
Requisitos para la serialización
Para que un objeto sea serializable, debe cumplir ciertos requisitos según el método de serialización elegido:
using System;
// Serialización binaria (tradicional)
[Serializable]
public class PersonaBinaria
{
public string Nombre { get; set; }
public int Edad { get; set; }
// Campos que no se serializan
[NonSerialized]
private string campoTemporal = "temporal";
}
// Serialización XML
public class PersonaXml
{
public string Nombre { get; set; }
public int Edad { get; set; }
// Debe tener constructor sin parámetros para XML
public PersonaXml() { }
}
// Serialización JSON (moderna)
public class PersonaJson
{
public string Nombre { get; set; }
public int Edad { get; set; }
public DateTime FechaNacimiento { get; set; }
}
Serialización XML
La serialización XML proporciona un formato estándar, legible y multiplataforma para persistir objetos. Es especialmente útil para archivos de configuración y cuando necesitamos interoperabilidad con otros sistemas.
Serialización XML básica
using System;
using System.IO;
using System.Xml.Serialization;
using System.Collections.Generic;
[Serializable]
public class Producto
{
public int Id { get; set; }
public string Nombre { get; set; }
public decimal Precio { get; set; }
public DateTime FechaCreacion { get; set; }
public List<string> Categorias { get; set; }
// Constructor requerido para XML
public Producto()
{
Categorias = new List<string>();
}
public Producto(int id, string nombre, decimal precio) : this()
{
Id = id;
Nombre = nombre;
Precio = precio;
FechaCreacion = DateTime.Now;
}
}
class Program
{
static void Main()
{
// Crear objetos de ejemplo
var productos = new List<Producto>
{
new Producto(1, "Laptop Dell", 899.99m),
new Producto(2, "Mouse Logitech", 25.50m),
new Producto(3, "Teclado mecánico", 120.00m)
};
// Agregar categorías
productos[0].Categorias.AddRange(new[] { "Electrónicos", "Informática" });
productos[1].Categorias.AddRange(new[] { "Electrónicos", "Accesorios" });
productos[2].Categorias.AddRange(new[] { "Electrónicos", "Periféricos" });
// Serializar a XML
SerializarAXml(productos, "productos.xml");
// Deserializar desde XML
var productosDeserializados = DeserializarDesdeXml<List<Producto>>("productos.xml");
// Mostrar resultados
MostrarProductos(productosDeserializados);
}
static void SerializarAXml<T>(T objeto, string archivo)
{
try
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
using (FileStream stream = new FileStream(archivo, FileMode.Create))
{
serializer.Serialize(stream, objeto);
}
Console.WriteLine($"Objeto serializado exitosamente en {archivo}");
// Mostrar el contenido XML generado
string contenidoXml = File.ReadAllText(archivo);
Console.WriteLine("\nContenido XML generado:");
Console.WriteLine(contenidoXml);
}
catch (Exception ex)
{
Console.WriteLine($"Error al serializar: {ex.Message}");
}
}
static T DeserializarDesdeXml<T>(string archivo) where T : class
{
try
{
if (!File.Exists(archivo))
{
Console.WriteLine($"El archivo {archivo} no existe.");
return null;
}
XmlSerializer serializer = new XmlSerializer(typeof(T));
using (FileStream stream = new FileStream(archivo, FileMode.Open))
{
T objeto = (T)serializer.Deserialize(stream);
Console.WriteLine($"\nObjeto deserializado exitosamente desde {archivo}");
return objeto;
}
}
catch (Exception ex)
{
Console.WriteLine($"Error al deserializar: {ex.Message}");
return null;
}
}
static void MostrarProductos(List<Producto> productos)
{
if (productos == null)
{
Console.WriteLine("No hay productos para mostrar.");
return;
}
Console.WriteLine($"\n=== PRODUCTOS DESERIALIZADOS ({productos.Count}) ===");
foreach (var producto in productos)
{
Console.WriteLine($"ID: {producto.Id}");
Console.WriteLine($"Nombre: {producto.Nombre}");
Console.WriteLine($"Precio: {producto.Precio:C}");
Console.WriteLine($"Fecha de creación: {producto.FechaCreacion:dd/MM/yyyy HH:mm}");
Console.WriteLine($"Categorías: {string.Join(", ", producto.Categorias)}");
Console.WriteLine(new string('-', 40));
}
}
}
Personalización de la serialización XML
using System;
using System.IO;
using System.Xml.Serialization;
[XmlRoot("Empleado")] // Personalizar el elemento raíz
public class EmpleadoXml
{
[XmlAttribute("id")] // Serializar como atributo XML
public int Id { get; set; }
[XmlElement("NombreCompleto")] // Personalizar nombre del elemento
public string Nombre { get; set; }
[XmlElement("Puesto")]
public string Cargo { get; set; }
[XmlIgnore] // No incluir en la serialización XML
public string ContrasenaInterna { get; set; }
[XmlArray("ListaHabilidades")] // Personalizar el contenedor del array
[XmlArrayItem("Habilidad")] // Personalizar cada elemento del array
public string[] Habilidades { get; set; }
[XmlElement("FechaContratacion")]
public DateTime FechaIngreso { get; set; }
// Constructor requerido
public EmpleadoXml()
{
Habilidades = new string[0];
}
}
class ProgramPersonalizadoXml
{
static void Main()
{
var empleado = new EmpleadoXml
{
Id = 1001,
Nombre = "María González Pérez",
Cargo = "Desarrolladora Senior",
ContrasenaInterna = "secreto123", // No aparecerá en XML
Habilidades = new[] { "C#", ".NET Core", "Azure", "SQL Server" },
FechaIngreso = new DateTime(2019, 6, 15)
};
// Serializar con personalización
SerializarEmpleadoXml(empleado, "empleado_personalizado.xml");
// Deserializar
var empleadoDeserializado = DeserializarEmpleadoXml("empleado_personalizado.xml");
if (empleadoDeserializado != null)
{
MostrarEmpleado(empleadoDeserializado);
}
}
static void SerializarEmpleadoXml(EmpleadoXml empleado, string archivo)
{
try
{
XmlSerializer serializer = new XmlSerializer(typeof(EmpleadoXml));
using (FileStream stream = new FileStream(archivo, FileMode.Create))
using (StreamWriter writer = new StreamWriter(stream, System.Text.Encoding.UTF8))
{
serializer.Serialize(writer, empleado);
}
Console.WriteLine($"Empleado serializado en {archivo}");
// Mostrar XML generado
string xml = File.ReadAllText(archivo);
Console.WriteLine("\nXML personalizado generado:");
Console.WriteLine(xml);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
static EmpleadoXml DeserializarEmpleadoXml(string archivo)
{
try
{
XmlSerializer serializer = new XmlSerializer(typeof(EmpleadoXml));
using (FileStream stream = new FileStream(archivo, FileMode.Open))
{
return (EmpleadoXml)serializer.Deserialize(stream);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error al deserializar: {ex.Message}");
return null;
}
}
static void MostrarEmpleado(EmpleadoXml empleado)
{
Console.WriteLine("\n=== EMPLEADO DESERIALIZADO ===");
Console.WriteLine($"ID: {empleado.Id}");
Console.WriteLine($"Nombre: {empleado.Nombre}");
Console.WriteLine($"Cargo: {empleado.Cargo}");
Console.WriteLine($"Fecha de ingreso: {empleado.FechaIngreso:dd/MM/yyyy}");
Console.WriteLine($"Habilidades: {string.Join(", ", empleado.Habilidades)}");
Console.WriteLine($"Contraseña interna: '{empleado.ContrasenaInterna}' (debe estar vacía)");
}
}
Serialización binaria moderna
Aunque la serialización binaria tradicional (BinaryFormatter
) está obsoleta por razones de seguridad, podemos implementar serialización binaria personalizada o usar librerías modernas como MessagePack.
Implementación de serialización binaria personalizada
using System;
using System.IO;
using System.Text;
public class PersonaBinaria
{
public int Id { get; set; }
public string Nombre { get; set; }
public DateTime FechaNacimiento { get; set; }
public decimal Salario { get; set; }
public bool Activo { get; set; }
// Método para serializar a binario
public byte[] SerializarABinario()
{
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream, Encoding.UTF8))
{
// Escribir cada campo en orden específico
writer.Write(Id);
writer.Write(Nombre ?? string.Empty);
writer.Write(FechaNacimiento.ToBinary());
writer.Write(Salario);
writer.Write(Activo);
return stream.ToArray();
}
}
// Método para deserializar desde binario
public static PersonaBinaria DeserializarDesdeBinario(byte[] datos)
{
using (var stream = new MemoryStream(datos))
using (var reader = new BinaryReader(stream, Encoding.UTF8))
{
return new PersonaBinaria
{
Id = reader.ReadInt32(),
Nombre = reader.ReadString(),
FechaNacimiento = DateTime.FromBinary(reader.ReadInt64()),
Salario = reader.ReadDecimal(),
Activo = reader.ReadBoolean()
};
}
}
}
class ProgramBinario
{
static void Main()
{
var persona = new PersonaBinaria
{
Id = 1,
Nombre = "Carlos Mendoza",
FechaNacimiento = new DateTime(1985, 8, 20),
Salario = 45000.75m,
Activo = true
};
Console.WriteLine("=== SERIALIZACIÓN BINARIA PERSONALIZADA ===");
// Serializar a binario
byte[] datosBinarios = persona.SerializarABinario();
// Guardar en archivo
File.WriteAllBytes("persona_binaria.dat", datosBinarios);
Console.WriteLine($"Persona serializada a binario ({datosBinarios.Length} bytes)");
Console.WriteLine($"Datos binarios (hex): {Convert.ToHexString(datosBinarios)}");
// Deserializar desde binario
byte[] datosLeidos = File.ReadAllBytes("persona_binaria.dat");
PersonaBinaria personaDeserializada = PersonaBinaria.DeserializarDesdeBinario(datosLeidos);
// Verificar integridad
Console.WriteLine("\n=== VERIFICACIÓN DE INTEGRIDAD ===");
Console.WriteLine($"ID original: {persona.Id} | Deserializado: {personaDeserializada.Id}");
Console.WriteLine($"Nombre original: {persona.Nombre} | Deserializado: {personaDeserializada.Nombre}");
Console.WriteLine($"Fecha original: {persona.FechaNacimiento:dd/MM/yyyy} | Deserializada: {personaDeserializada.FechaNacimiento:dd/MM/yyyy}");
Console.WriteLine($"Salario original: {persona.Salario:C} | Deserializado: {personaDeserializada.Salario:C}");
Console.WriteLine($"Activo original: {persona.Activo} | Deserializado: {personaDeserializada.Activo}");
// Comparar tamaños
CompararTamanos(persona);
}
static void CompararTamanos(PersonaBinaria persona)
{
Console.WriteLine("\n=== COMPARACIÓN DE TAMAÑOS ===");
// Binario personalizado
byte[] binario = persona.SerializarABinario();
Console.WriteLine($"Binario personalizado: {binario.Length} bytes");
// JSON (usando System.Text.Json)
string json = System.Text.Json.JsonSerializer.Serialize(persona);
byte[] jsonBytes = Encoding.UTF8.GetBytes(json);
Console.WriteLine($"JSON: {jsonBytes.Length} bytes");
Console.WriteLine($"Contenido JSON: {json}");
// XML
var xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(PersonaBinaria));
using (var stringWriter = new StringWriter())
{
xmlSerializer.Serialize(stringWriter, persona);
string xml = stringWriter.ToString();
byte[] xmlBytes = Encoding.UTF8.GetBytes(xml);
Console.WriteLine($"XML: {xmlBytes.Length} bytes");
}
}
}
Serialización con tipos complejos y jerarquías
Cuando trabajamos con objetos que contienen otros objetos, herencia o interfaces, la serialización requiere consideraciones adicionales.
Manejo de herencia y polimorfismo
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.IO;
using System.Collections.Generic;
// Clase base abstracta
[JsonDerivedType(typeof(Empleado), typeDiscriminator: "empleado")]
[JsonDerivedType(typeof(Gerente), typeDiscriminator: "gerente")]
[JsonDerivedType(typeof(Desarrollador), typeDiscriminator: "desarrollador")]
public abstract class Persona
{
public int Id { get; set; }
public string Nombre { get; set; }
public string Email { get; set; }
public abstract void MostrarInformacion();
}
// Clases derivadas
public class Empleado : Persona
{
public string Departamento { get; set; }
public decimal SalarioBase { get; set; }
public override void MostrarInformacion()
{
Console.WriteLine($"Empleado: {Nombre}, Depto: {Departamento}, Salario: {SalarioBase:C}");
}
}
public class Gerente : Empleado
{
public int NumeroEmpleadosACargo { get; set; }
public decimal BonoGerencial { get; set; }
public override void MostrarInformacion()
{
Console.WriteLine($"Gerente: {Nombre}, Depto: {Departamento}, Empleados a cargo: {NumeroEmpleadosACargo}, Bono: {BonoGerencial:C}");
}
}
public class Desarrollador : Empleado
{
public string[] LenguajesProgramacion { get; set; }
public int AnosExperiencia { get; set; }
public Desarrollador()
{
LenguajesProgramacion = new string[0];
}
public override void MostrarInformacion()
{
Console.WriteLine($"Desarrollador: {Nombre}, Lenguajes: [{string.Join(", ", LenguajesProgramacion)}], Experiencia: {AnosExperiencia} años");
}
}
class ProgramJerarquia
{
static void Main()
{
// Crear una colección polimórfica
var personas = new List<Persona>
{
new Empleado
{
Id = 1,
Nombre = "Ana López",
Email = "ana.lopez@empresa.com",
Departamento = "Recursos Humanos",
SalarioBase = 35000
},
new Gerente
{
Id = 2,
Nombre = "Roberto García",
Email = "roberto.garcia@empresa.com",
Departamento = "Tecnología",
SalarioBase = 60000,
NumeroEmpleadosACargo = 12,
BonoGerencial = 15000
},
new Desarrollador
{
Id = 3,
Nombre = "Laura Martín",
Email = "laura.martin@empresa.com",
Departamento = "Desarrollo",
SalarioBase = 45000,
LenguajesProgramacion = new[] { "C#", "JavaScript", "Python", "SQL" },
AnosExperiencia = 5
}
};
Console.WriteLine("=== SERIALIZACIÓN DE JERARQUÍA DE CLASES ===");
// Serializar la colección polimórfica
SerializarJerarquia(personas, "equipo.json");
// Deserializar manteniendo los tipos
var personasDeserializadas = DeserializarJerarquia("equipo.json");
// Mostrar información utilizando polimorfismo
MostrarEquipo(personasDeserializadas);
// Analizar tipos después de la deserialización
AnalizarTipos(personasDeserializadas);
}
static void SerializarJerarquia(List<Persona> personas, string archivo)
{
try
{
var opciones = new JsonSerializerOptions
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
string json = JsonSerializer.Serialize(personas, opciones);
File.WriteAllText(archivo, json);
Console.WriteLine($"Jerarquía serializada en {archivo}");
Console.WriteLine("\nContenido JSON generado:");
Console.WriteLine(json);
}
catch (Exception ex)
{
Console.WriteLine($"Error al serializar: {ex.Message}");
}
}
static List<Persona> DeserializarJerarquia(string archivo)
{
try
{
if (!File.Exists(archivo))
{
Console.WriteLine($"El archivo {archivo} no existe.");
return new List<Persona>();
}
string json = File.ReadAllText(archivo);
var personas = JsonSerializer.Deserialize<List<Persona>>(json);
Console.WriteLine($"\n{personas?.Count ?? 0} personas deserializadas correctamente");
return personas ?? new List<Persona>();
}
catch (Exception ex)
{
Console.WriteLine($"Error al deserializar: {ex.Message}");
return new List<Persona>();
}
}
static void MostrarEquipo(List<Persona> personas)
{
Console.WriteLine("\n=== INFORMACIÓN DEL EQUIPO (POLIMORFISMO) ===");
foreach (var persona in personas)
{
Console.WriteLine($"Tipo: {persona.GetType().Name}");
persona.MostrarInformacion(); // Llamada polimórfica
Console.WriteLine($"Email: {persona.Email}");
Console.WriteLine(new string('-', 50));
}
}
static void AnalizarTipos(List<Persona> personas)
{
Console.WriteLine("\n=== ANÁLISIS DE TIPOS DESPUÉS DE DESERIALIZACIÓN ===");
var conteoTipos = new Dictionary<string, int>();
decimal sueldoTotal = 0;
int totalDesarrolladores = 0;
foreach (var persona in personas)
{
string tipoNombre = persona.GetType().Name;
conteoTipos[tipoNombre] = conteoTipos.GetValueOrDefault(tipoNombre, 0) + 1;
// Usar pattern matching para analizar tipos específicos
switch (persona)
{
case Gerente gerente:
sueldoTotal += gerente.SalarioBase + gerente.BonoGerencial;
Console.WriteLine($"Gerente {gerente.Nombre} gestiona {gerente.NumeroEmpleadosACargo} empleados");
break;
case Desarrollador dev:
sueldoTotal += dev.SalarioBase;
totalDesarrolladores++;
Console.WriteLine($"Desarrollador {dev.Nombre} conoce {dev.LenguajesProgramacion.Length} lenguajes");
break;
case Empleado emp:
sueldoTotal += emp.SalarioBase;
Console.WriteLine($"Empleado {emp.Nombre} en {emp.Departamento}");
break;
}
}
Console.WriteLine($"\nResumen:");
Console.WriteLine($"Total de personas: {personas.Count}");
foreach (var kvp in conteoTipos)
{
Console.WriteLine($"- {kvp.Key}: {kvp.Value}");
}
Console.WriteLine($"Costo total en sueldos: {sueldoTotal:C}");
Console.WriteLine($"Desarrolladores: {totalDesarrolladores}");
}
}
Serialización personalizada avanzada
Para casos específicos donde necesitamos control total sobre el proceso de serialización, podemos implementar conversores personalizados.
Conversor personalizado para System.Text.Json
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.IO;
// Clase que necesita serialización personalizada
public class ConfiguracionCompleja
{
public string Nombre { get; set; }
public TimeSpan TiempoEspera { get; set; }
public Version VersionApp { get; set; }
public DirectoryInfo DirectorioTrabajo { get; set; }
public Dictionary<string, object> ConfiguracionExtra { get; set; }
public ConfiguracionCompleja()
{
ConfiguracionExtra = new Dictionary<string, object>();
}
}
// Conversor personalizado para TimeSpan
public class TimeSpanConverter : JsonConverter<TimeSpan>
{
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
string valor = reader.GetString();
return TimeSpan.Parse(valor);
}
public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(@"hh\:mm\:ss"));
}
}
// Conversor personalizado para Version
public class VersionConverter : JsonConverter<Version>
{
public override Version Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
string valor = reader.GetString();
return new Version(valor);
}
public override void Write(Utf8JsonWriter writer, Version value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
// Conversor personalizado para DirectoryInfo
public class DirectoryInfoConverter : JsonConverter<DirectoryInfo>
{
public override DirectoryInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
string ruta = reader.GetString();
return string.IsNullOrEmpty(ruta) ? null : new DirectoryInfo(ruta);
}
public override void Write(Utf8JsonWriter writer, DirectoryInfo value, JsonSerializerOptions options)
{
writer.WriteStringValue(value?.FullName ?? "");
}
}
class ProgramConversorPersonalizado
{
static void Main()
{
var configuracion = new ConfiguracionCompleja
{
Nombre = "Mi Aplicación Enterprise",
TiempoEspera = TimeSpan.FromMinutes(30),
VersionApp = new Version(2, 1, 5, 0),
DirectorioTrabajo = new DirectoryInfo(@"C:\MiApp\Data"),
ConfiguracionExtra = new Dictionary<string, object>
{
{ "maxConexiones", 100 },
{ "habilitarLog", true },
{ "nivelLog", "Info" },
{ "configuracionAvanzada", new { timeout = 5000, reintentos = 3 } }
}
};
Console.WriteLine("=== SERIALIZACIÓN CON CONVERSORES PERSONALIZADOS ===");
// Configurar opciones con conversores personalizados
var opciones = new JsonSerializerOptions
{
WriteIndented = true,
Converters =
{
new TimeSpanConverter(),
new VersionConverter(),
new DirectoryInfoConverter()
}
};
// Serializar
string json = JsonSerializer.Serialize(configuracion, opciones);
Console.WriteLine("Configuración serializada:");
Console.WriteLine(json);
// Guardar en archivo
File.WriteAllText("configuracion_compleja.json", json);
// Deserializar
string jsonLeido = File.ReadAllText("configuracion_compleja.json");
ConfiguracionCompleja configDeserializada = JsonSerializer.Deserialize<ConfiguracionCompleja>(jsonLeido, opciones);
// Verificar integridad de la deserialización
VerificarConfiguracion(configuracion, configDeserializada);
// Mostrar uso práctico
UsarConfiguracion(configDeserializada);
}
static void VerificarConfiguracion(ConfiguracionCompleja original, ConfiguracionCompleja deserializada)
{
Console.WriteLine("\n=== VERIFICACIÓN DE INTEGRIDAD ===");
Console.WriteLine($"Nombre: {original.Nombre} == {deserializada.Nombre} -> {original.Nombre == deserializada.Nombre}");
Console.WriteLine($"Tiempo espera: {original.TiempoEspera} == {deserializada.TiempoEspera} -> {original.TiempoEspera == deserializada.TiempoEspera}");
Console.WriteLine($"Versión: {original.VersionApp} == {deserializada.VersionApp} -> {original.VersionApp.ToString() == deserializada.VersionApp.ToString()}");
Console.WriteLine($"Directorio: {original.DirectorioTrabajo?.FullName} == {deserializada.DirectorioTrabajo?.FullName} -> {original.DirectorioTrabajo?.FullName == deserializada.DirectorioTrabajo?.FullName}");
Console.WriteLine($"Configuración extra: {original.ConfiguracionExtra.Count} elementos == {deserializada.ConfiguracionExtra.Count} elementos");
}
static void UsarConfiguracion(ConfiguracionCompleja config)
{
Console.WriteLine("\n=== USO PRÁCTICO DE LA CONFIGURACIÓN ===");
Console.WriteLine($"Iniciando {config.Nombre} v{config.VersionApp}");
Console.WriteLine($"Directorio de trabajo: {config.DirectorioTrabajo?.FullName}");
Console.WriteLine($"Tiempo de espera configurado: {config.TiempoEspera}");
// Usar la configuración extra
if (config.ConfiguracionExtra.TryGetValue("maxConexiones", out object maxConn))
{
Console.WriteLine($"Máximo de conexiones: {maxConn}");
}
if (config.ConfiguracionExtra.TryGetValue("habilitarLog", out object logEnabled) && (bool)logEnabled)
{
Console.WriteLine($"Logging habilitado - Nivel: {config.ConfiguracionExtra["nivelLog"]}");
}
// Verificar si el directorio existe
if (config.DirectorioTrabajo != null)
{
if (config.DirectorioTrabajo.Exists)
{
Console.WriteLine("✓ Directorio de trabajo existe y es accesible");
}
else
{
Console.WriteLine("⚠ Advertencia: El directorio de trabajo no existe");
}
}
}
}
Técnicas de optimización y mejores prácticas
Para aplicaciones que manejan gran volumen de datos o requieren alta performance, es importante optimizar el proceso de serialización.
Optimización de memoria y performance
using System;
using System.Text.Json;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Diagnostics;
public class DatosGrandes
{
public int Id { get; set; }
public string Nombre { get; set; }
public double[] ValoresNumericos { get; set; }
public DateTime FechaCreacion { get; set; }
}
class OptimizacionSerializacion
{
static async Task Main()
{
Console.WriteLine("=== OPTIMIZACIÓN DE SERIALIZACIÓN ===");
// Generar datos de prueba
var datosGrandes = GenerarDatosPrueba(10000);
Console.WriteLine($"Generados {datosGrandes.Count} registros para pruebas de performance");
// Comparar diferentes métodos de serialización
await CompararMetodosSerializacion(datosGrandes);
// Demostrar serialización con streams
await DemostrarStreaming(datosGrandes);
// Optimizaciones específicas
DemostrarOptimizaciones();
}
static List<DatosGrandes> GenerarDatosPrueba(int cantidad)
{
var datos = new List<DatosGrandes>();
var random = new Random();
for (int i = 0; i < cantidad; i++)
{
var valores = new double[50]; // Array de 50 valores por registro
for (int j = 0; j < valores.Length; j++)
{
valores[j] = random.NextDouble() * 1000;
}
datos.Add(new DatosGrandes
{
Id = i + 1,
Nombre = $"Registro_{i + 1:D6}",
ValoresNumericos = valores,
FechaCreacion = DateTime.Now.AddDays(-random.Next(365))
});
}
return datos;
}
static async Task CompararMetodosSerializacion(List<DatosGrandes> datos)
{
Console.WriteLine("\n=== COMPARACIÓN DE MÉTODOS DE SERIALIZACIÓN ===");
// Método 1: Serialización síncrona en memoria
var sw = Stopwatch.StartNew();
var opcionesCompactas = new JsonSerializerOptions { WriteIndented = false };
string jsonEnMemoria = JsonSerializer.Serialize(datos, opcionesCompactas);
sw.Stop();
Console.WriteLine($"Serialización en memoria: {sw.ElapsedMilliseconds} ms, Tamaño: {jsonEnMemoria.Length / 1024} KB");
// Método 2: Serialización directa a archivo
sw.Restart();
using (var stream = new FileStream("datos_directos.json", FileMode.Create))
{
await JsonSerializer.SerializeAsync(stream, datos, opcionesCompactas);
}
sw.Stop();
var tamanoArchivo = new FileInfo("datos_directos.json").Length;
Console.WriteLine($"Serialización a stream: {sw.ElapsedMilliseconds} ms, Tamaño: {tamanoArchivo / 1024} KB");
// Método 3: Deserialización desde stream
sw.Restart();
List<DatosGrandes> datosDeserializados;
using (var stream = new FileStream("datos_directos.json", FileMode.Open))
{
datosDeserializados = await JsonSerializer.DeserializeAsync<List<DatosGrandes>>(stream);
}
sw.Stop();
Console.WriteLine($"Deserialización desde stream: {sw.ElapsedMilliseconds} ms, Registros: {datosDeserializados?.Count ?? 0}");
// Método 4: Con opciones optimizadas
sw.Restart();
var opcionesOptimizadas = new JsonSerializerOptions
{
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
NumberHandling = JsonNumberHandling.AllowReadingFromString
};
using (var stream = new FileStream("datos_optimizados.json", FileMode.Create))
{
await JsonSerializer.SerializeAsync(stream, datos, opcionesOptimizadas);
}
sw.Stop();
var tamanoOptimizado = new FileInfo("datos_optimizados.json").Length;
Console.WriteLine($"Serialización optimizada: {sw.ElapsedMilliseconds} ms, Tamaño: {tamanoOptimizado / 1024} KB");
}
static async Task DemostrarStreaming(List<DatosGrandes> datos)
{
Console.WriteLine("\n=== PROCESAMIENTO CON STREAMING ===");
// Simular procesamiento de streaming escribiendo un registro a la vez
var sw = Stopwatch.StartNew();
using (var stream = new FileStream("datos_streaming.json", FileMode.Create))
using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = false }))
{
writer.WriteStartArray();
for (int i = 0; i < datos.Count; i++)
{
// Escribir cada objeto individualmente
JsonSerializer.Serialize(writer, datos[i]);
// Simular procesamiento adicional cada 1000 registros
if (i > 0 && i % 1000 == 0)
{
await writer.FlushAsync();
Console.WriteLine($"Procesados {i} registros...");
}
}
writer.WriteEndArray();
}
sw.Stop();
Console.WriteLine($"Streaming completado en {sw.ElapsedMilliseconds} ms");
// Leer usando streaming
await LeerConStreaming("datos_streaming.json");
}
static async Task LeerConStreaming(string archivo)
{
Console.WriteLine($"\nLeyendo {archivo} con streaming...");
using var stream = new FileStream(archivo, FileMode.Open);
using var document = await JsonDocument.ParseAsync(stream);
var root = document.RootElement;
if (root.ValueKind == JsonValueKind.Array)
{
int contador = 0;
foreach (var elemento in root.EnumerateArray())
{
// Procesar cada elemento sin cargar todo en memoria
if (elemento.TryGetProperty("Id", out var idProp) &&
elemento.TryGetProperty("Nombre", out var nombreProp))
{
contador++;
// Mostrar progreso cada 2500 registros
if (contador % 2500 == 0)
{
Console.WriteLine($"Leído registro {contador}: ID={idProp.GetInt32()}, Nombre={nombreProp.GetString()}");
}
}
}
Console.WriteLine($"Total de registros procesados: {contador}");
}
}
static void DemostrarOptimizaciones()
{
Console.WriteLine("\n=== TÉCNICAS DE OPTIMIZACIÓN ESPECÍFICAS ===");
// 1. Reutilizar opciones de serialización
var opcionesReutilizables = new JsonSerializerOptions
{
WriteIndented = false,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
Console.WriteLine("✓ Reutilización de JsonSerializerOptions");
// 2. Usar ArrayPool para reducir allocaciones
Console.WriteLine("✓ Uso de ArrayPool recomendado para arrays grandes");
// 3. Configuraciones específicas para performance
var opcionesPerformance = new JsonSerializerOptions
{
DefaultBufferSize = 32 * 1024, // Buffer de 32KB para streams grandes
MaxDepth = 32, // Limitar profundidad para evitar stack overflow
AllowTrailingCommas = false, // Parsing más rápido
ReadCommentHandling = JsonCommentHandling.Skip
};
Console.WriteLine("✓ Configuraciones de performance aplicadas");
// 4. Demostrar medición de memoria
MedirUsoMemoria();
}
static void MedirUsoMemoria()
{
Console.WriteLine("\n=== MEDICIÓN DE USO DE MEMORIA ===");
// Obtener memoria inicial
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
long memoriaInicial = GC.GetTotalMemory(false);
Console.WriteLine($"Memoria inicial: {memoriaInicial / 1024} KB");
// Crear datos y serializar
var datos = GenerarDatosPrueba(1000);
string json = JsonSerializer.Serialize(datos);
long memoriaConDatos = GC.GetTotalMemory(false);
Console.WriteLine($"Memoria con datos: {memoriaConDatos / 1024} KB");
Console.WriteLine($"Memoria adicional utilizada: {(memoriaConDatos - memoriaInicial) / 1024} KB");
// Liberar referencias
datos = null;
json = null;
// Forzar garbage collection
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
long memoriaFinal = GC.GetTotalMemory(false);
Console.WriteLine($"Memoria después de GC: {memoriaFinal / 1024} KB");
Console.WriteLine($"Memoria liberada: {(memoriaConDatos - memoriaFinal) / 1024} KB");
}
}
Comparación de formatos de serialización
Para ayudar en la elección del formato más adecuado según las necesidades del proyecto, es útil comparar las características de cada uno.
Análisis comparativo detallado
using System;
using System.Text.Json;
using System.Xml.Serialization;
using System.IO;
using System.Diagnostics;
using System.Text;
public class ProductoComparacion
{
public int Id { get; set; }
public string Nombre { get; set; }
public string Descripcion { get; set; }
public decimal Precio { get; set; }
public DateTime FechaCreacion { get; set; }
public string[] Categorias { get; set; }
public bool Disponible { get; set; }
public ProductoComparacion()
{
Categorias = new string[0];
}
}
class ComparacionFormatos
{
static void Main()
{
var producto = new ProductoComparacion
{
Id = 1001,
Nombre = "Laptop Gaming Premium",
Descripcion = "Laptop de alto rendimiento con procesador Intel Core i9, 32GB RAM, SSD 1TB, tarjeta gráfica RTX 4080, pantalla 17.3 pulgadas 4K, teclado RGB mecánico, y sistema de refrigeración avanzado.",
Precio = 2499.99m,
FechaCreacion = DateTime.Now,
Categorias = new[] { "Electrónicos", "Informática", "Gaming", "Alto Rendimiento" },
Disponible = true
};
Console.WriteLine("=== COMPARACIÓN DE FORMATOS DE SERIALIZACIÓN ===");
Console.WriteLine($"Objeto a serializar: {producto.Nombre}");
Console.WriteLine($"Descripción: {producto.Descripcion.Substring(0, Math.Min(50, producto.Descripcion.Length))}...");
// Comparar todos los formatos
CompararJSON(producto);
CompararXML(producto);
CompararBinarioPersonalizado(producto);
// Resumen comparativo
MostrarResumenComparativo();
}
static void CompararJSON(ProductoComparacion producto)
{
Console.WriteLine("\n--- FORMATO JSON ---");
var sw = Stopwatch.StartNew();
// JSON compacto
var opcionesCompactas = new JsonSerializerOptions { WriteIndented = false };
string jsonCompacto = JsonSerializer.Serialize(producto, opcionesCompactas);
sw.Stop();
Console.WriteLine($"Tiempo de serialización: {sw.ElapsedMilliseconds} ms");
Console.WriteLine($"Tamaño compacto: {Encoding.UTF8.GetByteCount(jsonCompacto)} bytes");
Console.WriteLine($"JSON compacto: {jsonCompacto}");
// JSON formateado
sw.Restart();
var opcionesFormateadas = new JsonSerializerOptions { WriteIndented = true };
string jsonFormateado = JsonSerializer.Serialize(producto, opcionesFormateadas);
sw.Stop();
Console.WriteLine($"Tamaño formateado: {Encoding.UTF8.GetByteCount(jsonFormateado)} bytes");
Console.WriteLine($"JSON formateado:\n{jsonFormateado}");
// Deserialización
sw.Restart();
var productoDeserializado = JsonSerializer.Deserialize<ProductoComparacion>(jsonCompacto);
sw.Stop();
Console.WriteLine($"Tiempo de deserialización: {sw.ElapsedMilliseconds} ms");
Console.WriteLine($"Integridad: {productoDeserializado.Nombre == producto.Nombre}");
}
static void CompararXML(ProductoComparacion producto)
{
Console.WriteLine("\n--- FORMATO XML ---");
var serializer = new XmlSerializer(typeof(ProductoComparacion));
var sw = Stopwatch.StartNew();
// Serialización XML
using var stringWriter = new StringWriter();
serializer.Serialize(stringWriter, producto);
string xml = stringWriter.ToString();
sw.Stop();
Console.WriteLine($"Tiempo de serialización: {sw.ElapsedMilliseconds} ms");
Console.WriteLine($"Tamaño XML: {Encoding.UTF8.GetByteCount(xml)} bytes");
Console.WriteLine($"XML generado:\n{xml}");
// Deserialización XML
sw.Restart();
using var stringReader = new StringReader(xml);
var productoDeserializado = (ProductoComparacion)serializer.Deserialize(stringReader);
sw.Stop();
Console.WriteLine($"Tiempo de deserialización: {sw.ElapsedMilliseconds} ms");
Console.WriteLine($"Integridad: {productoDeserializado.Nombre == producto.Nombre}");
}
static void CompararBinarioPersonalizado(ProductoComparacion producto)
{
Console.WriteLine("\n--- FORMATO BINARIO PERSONALIZADO ---");
var sw = Stopwatch.StartNew();
// Serialización binaria personalizada
byte[] datosBinarios;
using (var stream = new MemoryStream())
using (var writer = new BinaryWriter(stream, Encoding.UTF8))
{
writer.Write(producto.Id);
writer.Write(producto.Nombre ?? "");
writer.Write(producto.Descripcion ?? "");
writer.Write(producto.Precio);
writer.Write(producto.FechaCreacion.ToBinary());
writer.Write(producto.Categorias.Length);
foreach (var categoria in producto.Categorias)
{
writer.Write(categoria);
}
writer.Write(producto.Disponible);
datosBinarios = stream.ToArray();
}
sw.Stop();
Console.WriteLine($"Tiempo de serialización: {sw.ElapsedMilliseconds} ms");
Console.WriteLine($"Tamaño binario: {datosBinarios.Length} bytes");
Console.WriteLine($"Datos binarios (hex): {Convert.ToHexString(datosBinarios)[..Math.Min(100, Convert.ToHexString(datosBinarios).Length)]}...");
// Deserialización binaria
sw.Restart();
ProductoComparacion productoDeserializado;
using (var stream = new MemoryStream(datosBinarios))
using (var reader = new BinaryReader(stream, Encoding.UTF8))
{
productoDeserializado = new ProductoComparacion
{
Id = reader.ReadInt32(),
Nombre = reader.ReadString(),
Descripcion = reader.ReadString(),
Precio = reader.ReadDecimal(),
FechaCreacion = DateTime.FromBinary(reader.ReadInt64()),
Disponible = reader.ReadBoolean()
};
// Leer categorías
int numCategorias = reader.ReadInt32();
productoDeserializado.Categorias = new string[numCategorias];
for (int i = 0; i < numCategorias; i++)
{
productoDeserializado.Categorias[i] = reader.ReadString();
}
// Leer disponibilidad (ya leída arriba, corregir orden)
stream.Position -= sizeof(bool);
var categorias = productoDeserializado.Categorias;
productoDeserializado.Categorias = categorias;
productoDeserializado.Disponible = reader.ReadBoolean();
}
sw.Stop();
Console.WriteLine($"Tiempo de deserialización: {sw.ElapsedMilliseconds} ms");
Console.WriteLine($"Integridad: {productoDeserializado.Nombre == producto.Nombre}");
}
static void MostrarResumenComparativo()
{
Console.WriteLine("\n=== RESUMEN COMPARATIVO ===");
var comparacion = new[]
{
new { Formato = "JSON Compacto", Legibilidad = "Alta", Tamaño = "Medio", Velocidad = "Alta", Compatibilidad = "Excelente", UsoRecomendado = "APIs, configuración" },
new { Formato = "JSON Formateado", Legibilidad = "Excelente", Tamaño = "Grande", Velocidad = "Alta", Compatibilidad = "Excelente", UsoRecomendado = "Desarrollo, debug" },
new { Formato = "XML", Legibilidad = "Buena", Tamaño = "Grande", Velocidad = "Media", Compatibilidad = "Excelente", UsoRecomendado = "Estándares enterprise, SOAP" },
new { Formato = "Binario", Legibilidad = "Nula", Tamaño = "Pequeño", Velocidad = "Excelente", Compatibilidad = "Limitada", UsoRecomendado = "Performance crítica, redes" }
};
Console.WriteLine($"{"Formato",-15} {"Legibilidad",-12} {"Tamaño",-8} {"Velocidad",-10} {"Compatibilidad",-13} {"Uso Recomendado",-25}");
Console.WriteLine(new string('-', 90));
foreach (var item in comparacion)
{
Console.WriteLine($"{item.Formato,-15} {item.Legibilidad,-12} {item.Tamaño,-8} {item.Velocidad,-10} {item.Compatibilidad,-13} {item.UsoRecomendado,-25}");
}
Console.WriteLine("\n=== RECOMENDACIONES GENERALES ===");
Console.WriteLine("• JSON: Ideal para APIs web, configuraciones y intercambio de datos moderno");
Console.WriteLine("• XML: Mejor para sistemas legacy y cuando se requiere validación estricta");
Console.WriteLine("• Binario: Óptimo para alta performance y comunicación interna entre componentes .NET");
Console.WriteLine("• Considerar el contexto: legibilidad vs performance, compatibilidad vs eficiencia");
}
}
Resumen
La serialización y deserialización de objetos constituye un pilar fundamental en el desarrollo de aplicaciones modernas con C#. A través de este artículo hemos explorado las diferentes estrategias disponibles, desde la serialización XML tradicional hasta las técnicas binarias personalizadas y los conversores avanzados de System.Text.Json.
Cada formato de serialización tiene sus propias ventajas y limitaciones: JSON sobresale por su simplicidad y compatibilidad web, XML brinda robustez y validación estricta, mientras que los formatos binarios ofrecen máxima eficiencia en términos de velocidad y espacio. La elección del método apropiado depende de factores como los requisitos de performance, la necesidad de legibilidad, la compatibilidad con otros sistemas y las restricciones de la infraestructura.
Las técnicas de optimización que hemos cubierto, incluyendo el uso de streams para grandes volúmenes de datos, la reutilización de configuraciones y la implementación de conversores personalizados, nos permiten crear soluciones que escalen eficientemente. Con este conocimiento, estamos preparados para implementar sistemas de persistencia robustos, APIs eficientes y mecanismos de intercambio de datos que satisfagan las demandas de las aplicaciones empresariales modernas.