En el año 2010, Microsoft incluyó en .NET un nuevo tipo estático, llamado dynamic. Este tipo, según mi experiencia viendo código de otra gente en mi trabajo o en internet, es muy poco usado, pero si lo utilizamos en algunos escenarios puede ser una herramienta muy potente para hacer nuestro código más limpio y legible.

En este artículo, intentaré explicar que hace dynamic tan diferente del resto de tipos de .NET, y os daré un par de ejemplos claros de su potencia usándolo para hacer “dynamic overloads“.

Como siempre, puedes clonar el proyecto demo desde mi repositorio en BitBucket, o descargar el archivo .ZIP si no usas Git:

dynamic

La principal diferencia entre dynamic y el resto de tipos de .NET, es que dynamic no pasa el control de tipos estáticos en tiempo de compilación, comportándose como un object y dejando todas las validaciones de métodos y propiedades para el tiempo de ejecución. Puedes ver esa diferencia en este ejemplo:

Teniendo esta clase pública:

public class MyClass {
     public string Prop { get; set; }
}

Usando la misma clase:

MyClass mc = new MyClass();
// esto va vien
mc.Prop = "hello";
// Esto no compila ... 'AnotherProp' no existe!
mc.AnotherProp = "world";

Usando object:

object obj = new MyClass();
// esto tampoco compila porque "object" no tiene esa propiedad
obj.Prop = "hello";

Usando dynamic:

dynamic dynobj = new MyClass();
mc.Prop = "hello";
// Esto compila!! Por supuesto, lanzará una excepción
// en tiempo de ejecución porque no existe esa propiedad
md.AnotherProp = "world";

Vale, debes estar pensando: 41 lineas de atículo para enseñarme algo que no compila, o que en el mejor de los casos, explota en tiempo de ejecución? Gran artículo… pero espera! como siempre, lo mejor está por venir…

Dynamic overload

Como he dicho antes, en este artículo nos vamos a enfocar en uno de los usos del tipo dynamic que encuentro muy interesante: Dynamic overload o dicho en español, sobrecargas dinámicas…

Esta técnica hará que tu código sea más claro, mantenible y elegante. Ten en cuenta usar reflection y comprobación de tipos puede ser algo más rápido. Si estás muy muy preocupado por el rendimiento, y es crítico para tu aplicación, a lo mejor ésta solución, aunque sea más legible, no sea la mejor.

Base

Vamos a crear una situación base para resolver. Primero, usaremos el método “tradicional” para solucionarla, y después, lo haremos con sobrecargas dinámicas.

Tenemos una colección de objetos polimórfica que hereda de la misma clase, pero cuando la iteramos, necesitamos hacer cosas diferentes dependiendo del tipo heredado que sea esa instancia en particular. Por ejemplo, en una lista de clientes, podríamos tener personas físicas y compañías. Algo así:

public abstract class Customer
{
    public string CustomerCode { get; set; }
    public decimal CreditLine { get; set; }
}

public class PhysicalCustomer : Customer
{
    public string Name { get; set; }
    public string Surname { get; set; }
}

public class CompanyCustomer : Customer
{
    public string CompanyName {get; set; }
    public string ContactName { get; set; }
    public string ContactSurname { get; set; }
    public string Position { get; set; }
}

Ahora imaginemos que quiero enviar a todos mis clientes un email, con un saludo personalizado que dependerá del tipo de cliente al que se lo estoy enviando.

La solución “tradicional”

Si usamos la solución tradicional, deberemos comprobar el tipo individual de cada objeto al iterar la colección de esta manera:

private static void Greet(Customer obj)
{
    if (obj is PhysicalCustomer)
    {
        var cust = (PhysicalCustomer) obj;
        Console.WriteLine("Hola {0} {1}. Tienes {2:c} disponibles en tu línea de crédito.", 
            cust.Name, cust.Surname,cust.CreditLine);
    }
    else if (obj is CompanyCustomer)
    {
        var cust = (CompanyCustomer) obj;
        Console.WriteLine("Hola {0} {1}, {2} en {3}. Tienes {4:c} disponibles en tu linea de crédito para empresa.",
            cust.ContactName,cust.ContactSurname,cust.Position, cust.CompanyName,cust.CreditLine);
    }
}

Eh! Por qué no? Es una manera correcta, pero tiene sus inconvenientes. El primero y principal es que en algunos escenarios puedes tener muchos tipos de objeto heredando de Customer (o sea, muchos tipos de cliente). En ese caso acabarás con una lista de if (...) else if (....) else if (....) muy, muy larga, y eso repercutiría directamente en código mucho menos legible y menos mantenible.

Aquí es donde dynamic entra con fuerza…

La solución dinamizada

La solución de sobrecargas dinámicas para este escenario consiste en pasarle a Greet() un parámetro de tipo dynamic en vez de Customer. De esta manera el método no sabrá que tipo le hemos pasado hasta tiempo de ejecución. Esto nos permite hacer otra llamada a un método con tantas sobrecargas como tipos disponibles haya, que será “automágicamente” filtrado por .NET en tiempo de ejecución al que más coincida con el tipo del objeto que nos ha llegado.

Que? Suena complicado? Para nada… aquí va la implementación:

private static void DynGreet(PhysicalCustomer cust)
{
    Console.WriteLine("Hola {0} {1}. Tienes {2:c} disponibles en tu linea de crédito personal.",
        cust.Name, cust.Surname, cust.CreditLine);
}

private static void DynGreet(CompanyCustomer cust)
{
    Console.WriteLine("Hola {0} {1}, {2} en {3}. Tienes {4:c} disponibles en tu linea de crédito de empresa.",
        cust.ContactName, cust.ContactSurname, cust.Position, cust.CompanyName, cust.CreditLine);
}

// Esta sobrecarga es opcional, pero es una buena práctica para cazar
// llamadas no deseadas a este método. Cualquier tipo sin una sobrecarga
// propia, caerá aquí ya que todos los objetos de .NET heredan de "object"
private static void DynGreet(object cust)
{
    throw new ArgumentException(string.Format("Tipo de objeto erróneo ({0})",cust.GetType()));
}

private static void Greet(dynamic obj)
{
    // OOoopps... has enviado un null??? No me vale!
    if (obj == null) throw new NullReferenceException("obj");
    // .NET elegirá la mejor sobrecarga
    DynGreet(obj);
}

Podrías pensar. “Pero, podríamos haber hecho que ese dynamic fusese directamente object y funcionaría igual…”

Error! Si el método recibe un parámetro object seguirá con ese tipo durante el alcance del método, con lo cual, al llamar a DynGreet() siempre seleccionaría la sobrecarga que recibe un object y no PhysicalCustomer o CompanyCustomer.

Ahí reside la diferencia de usar dynamic: el tipo de objeto no se va a saber hasta el momento de ejecutarlo, con lo cual se seleccionará la sobrecarga del tipo realmente recibido como parámetro, no del “declarado” en el método.

Código completo de DynGreeter

Aquí tienes el código completo de esta mini demo por si quieres ejecutarla en tu pc. Asegúrate de usar .NET 4.0 o mayor sino no funcionará.

using System;
using System.Collections.Generic;

namespace DynGreeter
{

    public abstract class Customer
    {
        public string CustomerCode { get; set; }
        public decimal CreditLine { get; set; }
    }

    public class PhysicalCustomer : Customer
    {
        public string Name { get; set; }
        public string Surname { get; set; }
    }

    public class CompanyCustomer : Customer
    {
        public string CompanyName {get; set; }
        public string ContactName { get; set; }
        public string ContactSurname { get; set; }
        public string Position { get; set; }
    }


    class Program
    {
        private static void DynGreet(PhysicalCustomer cust)
        {
            Console.WriteLine("Hola {0} {1}. Tienes {2:c} disponibles en tu linea de crédito personal.",
                cust.Name, cust.Surname, cust.CreditLine);
        }

        private static void DynGreet(CompanyCustomer cust)
        {
            Console.WriteLine("Hola {0} {1}, {2} en {3}. Tienes {4:c} disponibles en tu linea de crédito de empresa.",
                cust.ContactName, cust.ContactSurname, cust.Position, cust.CompanyName, cust.CreditLine);
        }

       // Esta sobrecarga es opcional, pero es una buena práctica para cazar
       // llamadas no deseadas a este método. Cualquier tipo sin una sobrecarga
       // propia, caerá aquí ya que todos los objetos de .NET heredan de "object"
        private static void DynGreet(object cust)
        {
            throw new ArgumentException(string.Format("Wrong object type ({0})",cust.GetType()));
        }

        private static void Greet(dynamic obj)
        {
            // OOoopps... has enviado un null??? No me vale!
            if (obj == null) throw new NullReferenceException("obj");
            // .NET elegirá la mejor sobrecarga
            DynGreet(obj);
        }

        private static List<Customer> GetCustomers()
        {
            return new List<Customer>
            {
                new PhysicalCustomer()
                {
                    CustomerCode = "doejohn01",
                    Name = "John",
                    Surname = "Doe",
                    CreditLine = 2000
                },
                new CompanyCustomer()
                {
                    CustomerCode = "gmotors01",
                    CompanyName = "General Motors",
                    ContactName = "Rich",
                    ContactSurname = "Man",
                    Position = "CTO",
                    CreditLine = 50000
                }
            };
        }

        private static void PressAnyKey()
        {
            Console.WriteLine();
            Console.WriteLine("Pulsa una tecla...");
            Console.ReadKey();
        }

        static void Main(string[] args)
        {
            // Let's do the greetings!
            GetCustomers().ForEach(Greet);
            PressAnyKey();
        }
    }
}

Recapitulando, DynExceptionHandler

Aquí tienes otro ejemplo de un escenario bastante común. Manejo de excepciones. Queremos actuar diferente dependiendo del tipo de excepción que lance nuestra aplicación. La manera “tradicional” sería esta:

try
{
    LoQueVayasAHacer();
}
catch (NullReferenceException ex)
{
    // Código para NullReferenceException aquí
}
catch (DivideByZeroException ex)
{
    // Código para DivideByZeroException aquí
}
catch (NotImplementedException ex)
{
    // Código para NotImplementedException aquí
}
catch (Exception ex)
{
    // Código para exception por defecto aquí...
}

En vez de esta lista de cláusulas catch que podría alargarse sin fin, puedes crear una clase estática que maneje todas las excepciones de la manera que desees dependiendo del tipo de excepción, e incluso reutilizarla en varias partes de tu aplicación, de esta manera:

public class DynExceptionHandler
{
    private static void DynHandle(NullReferenceException ex)
    {
        Console.WriteLine("Se lanzó una excepción Null reference!");
        Trace.TraceError("{0}",ex);
    }

    private static void DynHandle(DivideByZeroException ex)
    {
        Console.WriteLine("Se lanzó una excepción Divide by zero!");
        Trace.TraceError("{0}", ex);
    }

    private static void DynHandle(NotImplementedException ex)
    {
        Console.WriteLine("Se lanzó una excepción Not implemented!");
        Trace.TraceError("{0}", ex);
    }

    private static void DynHandle(Exception ex)
    {
        Console.WriteLine("Se lanzó una excepción desconocida! {0}", ex.GetType());
        Trace.TraceError("{0}",ex);
    }

    private static void DynHandle(object ex)
    {
        Console.WriteLine("No es una excepción!!! {0}", ex.GetType());
        Trace.TraceError("{0}", ex);
    }

    public static void HandleException(dynamic ex)
    {
        if (ex == null) 
            DynHandle(new Exception("Null Exception thrown?",ex));
        DynHandle(ex);
        Console.WriteLine();
        Console.WriteLine();
    }
}

En esta clase, tienes todos los tipos de excepción que quieres controlar bien ordenados y colocados, y, lo mas importante es que puedes añadir nuevas sobrecargas en cualquier momento sin tener que cambiar ninguna linea de código de decisión…

Usando esta clase, tu código en la aplicación sera mucho, mucho más simple, y la gente que lo lea podrá enfocarse en qué hace y no en qué puede fallar…

try
{
    LoQueVayasAHacer();
}
catch (Exception ex)
{
    DynExceptionHandler.HandleException(ex);
}

Bonito eh?, esto es código elegante…

Descarga

Y se acabó la explicación!

En caso de que no lo vieses al principio (o que seas demasiado vago para volver arriba), pongo otra vez los enlaces al repositorio GIT y al ZIP del proyecto de DynExceptionHanler.

Gracias!

Hoy ha sido un artículo un poco más largo de lo normal, así que espero que mis esfuerzos no hayan sido en vano y hayas disfrutado con él. Este es un bonito truco para mantener tu código simple, legible y elegante… Como siempre, volveré con más artículos!

Paz!

Follow me

Joan Vilariño

Senior .NET Developer at Ohpen
Follow me