Este artículo hace parte de una iniciativa genial: el primer calendario de adviento de C# español. Cada día, durante 9 días, dos artículos nuevos sobre C# serán compartidos por la comunidad. Esta es mi contribución.
Escribir código es fácil.
Tomar un editor de texto, instalar algunas herramientas y empezar a programar es algo relativamente sencillo. Sin embargo, escribir buen código es otra historia.
Es aquí donde entran buenas prácticas (como el principio KISS y los principios SOLID), patrones de diseño y otros temas. Te ayudan no solo a escribir código que funcione, sino que tenga buenas características como mantenibilidad, claridad y organización.
Sin embargo, algunas veces estas ideas son muy generales, e ignoran las capacidades específicas que un lenguaje de programación pueda ofrecer a ese propósito de escribir mejor código. Por eso, en este artículo quiero mencionar 5 características que ofrece C# para alcanzar ese ideal de escribir “código bonito”.
1. Interfaces genéricas
En un lenguaje orientado a objetos, uno de los buenos principios a seguir es programar contra abstracciones; es decir, interfaces o clases abstractas. La razón es que esto te permite cambiar entre distintas implementaciones sin que tu código se vea afectado.
Tomemos como ejemplo un método que retorna IDictionary.
public IDictionary ObtenerItems() { //... }
Con solo hacer que retorne la interfaz y no un tipo concreto (como Hashtable o SortedList), le estás dando la flexibilidad al método de que cambie su implementación internamente sin afectar a quienes lo utilizan.
Hasta ahí, viste un beneficio de usar interfaces. ¿Qué pasa si traemos las interfaces genéricas?
Las interfaces genéricas te brindan algo adicional: poder asociar un tipo específico a su implementación.
interface Repositorio<T> { bool Crear(T entidad); bool Actualizar(T entidad); void Eliminar(T entidad); }
Esto te permite tener implementaciones muy claras, donde sabes qué tipo está manipulando la clase:
class RepositorioLibro : Repositorio<Libro> { public bool Actualizar(Libro entidad) { //... } public bool Crear(Libro entidad) { //... } public void Eliminar(Libro entidad) { //... } }
Si no existieran los genéricos, tendrías que estar continuamente convirtiendo y validando tipos de datos.
NOTA: lo puedes mejorar aún más cuando pones restricciones sobre los tipos.
2. Inicializadores de objetos
A la hora de la verdad, los inicializadores de objetos son solo sugar syntax; es decir, hacen más fácil de usar el lenguaje, pero a la hora de compilar no hace diferencia. Sin embargo, el hecho de que se pueda hacer esto:
Libro libroUno = new Libro { Nombre = "Refactoring", Autor = "Martin Fowler", Editorial = "Addison-Wesley" };
En lugar de:
Libro libroUno = new Libro(); libroUno.Nombre = "Refactoring"; libroUno.Autor = "Martin Fowler"; libroUno.Editorial = "Addison-Wesley";
No solo hace que sean menos caracteres, sino que deja claro los valores iniciales con que se quiere crear el objeto.
3. Interpolación de strings
Disponible desde C# 6.0.
La interpolación de strings o cadenas es otra funcionalidad que brinda mucha claridad al código. Antes de la interpolación, los dos enfoques comunes para unir valores a una cadena eran:
- Concatenar uno a uno los valores usando “+”. Esto se llega a tornar confuso cuando las cadenas son muy elaboradas.
- Usar String.Format y otros métodos similares. Aquí, se utilizan una serie de “tokens” para indicar donde insertar los valores y en qué formato.
Con la interpolación, se pueden poner expresiones de código dentro de una cadena, las cuales se compilan. Es decir, si el código dentro de la cadena tiene algún error de sintaxis, el programa no compila.
Este es un ejemplo donde se muestran las 3 alternativas:
//Interpolación Console.WriteLine($"El autor del libro '{libro.Nombre}' es {libro.Autor}");
//Concatenación Console.WriteLine("El autor del libro '" + libro.Nombre + "' es " + libro.Autor);
//Formato Console.WriteLine("El autor del libro '{0}' es {1}", libro.Nombre, libro.Autor);
4. Delegados
Los delegados son una característica muy potente pero que casi no se aprovecha. Su uso está muy ligado al manejo de eventos en entornos como Windows Forms, pero su uso va mucho más allá.
En términos simples, un delegado es una plantilla para la firma de un método. A diferencia de las clases o las interfaces, que tienen que ser explicitamente implementadas, un método no implementa el delegado que define su firma. Sin embargo, el compilador sabe si un método es acorde con un delegado o no.
¿Por qué considero que te ayuda a escribir mejor código?
Porque puedes empezar a hacer código más dinámico y mantenible, gracias a que los métodos se vuelven un tipo más dentro de la aplicación.
Te lo enseño con un ejemplo:
//Definición del delegado delegate bool NotificacionDelegado(Libro libro); static void Metodo() { Libro libro = new Libro { Nombre = "Refactoring", Autor = "Martin Fowler", Editorial = "Addison-Wesley", Estado = EstadoLibro.EnProgreso }; //Mapeo entre estados y delegados. var manejadores = new Dictionary<EstadoLibro, NotificacionDelegado> { { EstadoLibro.Anunciado, NotificarLibroAnunciado }, { EstadoLibro.EnProgreso, NotificarLibroEnProgreso }, { EstadoLibro.Publicado, NotificarLibroPublicado } }; //Se crea una variable del tipo del delegado, que apunta al método correspondiente. NotificacionDelegado delegado = manejadores[libro.Estado]; bool resultado = delegado(libro); } static bool NotificarLibroAnunciado(Libro libro) { //... } static bool NotificarLibroEnProgreso(Libro libro) { //... } static bool NotificarLibroPublicado(Libro libro) { //... }
En el ejemplo anterior, el delegado NotificacionDelegado ayuda a añadir dinámicamente métodos para cada uno de los estados del libro. Esto, con la gran ventaja de que el código existente no hay modificarlo.
5. Expresión nameof
Disponible desde C# 6.0.
Este palabra clave tiene una funcionalidad sencilla, pero interesante: obtener el nombre de la variable, tipo o miembro que se la pasa como parámetro.
Libro libro = new Libro(); string mensaje = "La variable se llama: " + nameof(libro);
El ejemplo quizá sea muy básico, pero ilustra un punto importante: el nombre de la variable no se está “quemando” dentro de la cadena. Si el día de mañana la variable cambia de nombre, este cambio se verá reflejado en la cadena automáticamente. No hay posibilidad de que se nos escape, porque no va a compilar.
Algunos escenarios más prácticos donde nameof tiene mucho sentido:
- Cuando se usa INotifyPropertyChanged, para indicar el nombre de la propiedad que cambió de valor.
- Crear un link a una acción en MVC, donde se requiere el nombre del método que corresponde a la acción.
- Para registrar el nombre de un método o una variable en un log.
Cierre
C# es un lenguaje muy rico y amplio con muchísimas funcionalidades. Estas en particular me parecen útiles, desde mi propia experiencia.
Y tú, ¿qué funcionalidad añadirías a la lista?