El futuro atributo CallerArgumentExpression: un deseo que se cumplirá

“… It’s too bad that all these things
Can only happen in my dreams
Only in dreams, in beautiful dreams …”
Roy Orbison, “In Dreams” (1963)

“… We all know / That people are the same wherever you go …”
Paul McCartney & Stevie Wonder, “Ebony and Ivory” (1982)

Hace unos cuantos meses, mi buen amigo Eduard Tomàs publicó en este mismo sitio una entrada con título auto-descriptivo llamada “Hoy he echado en falta poder definir macros en C#“. En la discusión subsiguiente, especulamos sobre un hipotético atributo de .NET que hiciera posible satisfacer su deseo incumplido. Pues bien, ayer revisando las propuestas de características a añadir en las próximas versiones del lenguaje (en el sitio de Github en el que el equipo de C# publica las minutas de sus reuniones, entre otros documentos muy interesantes) me encontré con la descripción del futuro atributo CallerArgumentExpression, que (en el supuesto de que sea implementado, claro), permitirá lograr fácilmente el efecto que Eduard quería obtener aquella vez.

La propuesta de esta nueva característica está siendo considerada por el equipo de C# desde mayo de 2017, unos cuantos meses antes de que nosotros conversáramos sobre el tema; ello me reafirma una vez más en que, como dijeran magistralmente Paul y Stevie, la gente (y en particular, los programadores) tiene las mismas necesidades en todos los lugares. 🙂

using System.Runtime.CompilerServices;

public static class Check
{
    public static void NotNull<TParameter>(TParameter param, 
        [CallerArgumentExpression("param")] string expr = "")
        where TParameter : class
    {
        if (param == null)
        {
            throw new ArgumentNullException(paramName: expr);
        }
    }
}

Referencia musical: Intenté infructuosamente encontrar alguna canción de mi época que dijera que algunos sueños a veces se cumplen; no encontré ninguna, así que he puesto otra que dice más o menos lo contrario. Su autor fue el genial Roy Orbison, a quien mayormente se le recuerda por haber escrito la canción que sirvió de tema a la película “Pretty Woman“. De Paul McCartney y Stevie Wonder no diré nada más aquí – son dos de mis músicos favoritos y seguramente los he mencionado ya en alguna entrada anterior.

La sentencia switch en C# 7.0 (y 2)

“It never rains in California,
But girl, don’t they warn ya,
It pours, man, it pours…”
Albert Hammond, It Never Rains in Southern California (1972)

Parte 1

En nuestra entrega anterior hablamos sobre las nuevas posibilidades de la sentencia switch, y en particular las relacionadas con la utilización de los tres tipos de patrones soportados por C# 7.0 en las cláusulas case, que mencionamos por primera vez aquí. En dicha última entrega nos quedó pendiente hablar sobre la utilización en el marco de las sentencias switch del tercer tipo de patrones disponible: los patrones var.

De manera similar a lo que ocurre cuando se les usa en una sentencia if, estos patrones casan incondicionalmente, y simplemente asignan el valor de la expresión de switch a la variable asociada, que será del mismo tipo de la expresión:

    switch (obj)
    {
        case var x:
            Console.WriteLine($”Type is {x.GetType().FullName});
            break;
    }

Como mismo dijimos entonces, no parece fácil encontrar una situación práctica donde la utilidad del uso de esta construcción sea evidente. Al menos, no he visto ninguno todavía en ninguno de los artículos que he leído al respecto (incluyendo los de MSDN donde se describe la característica). Pero ya sabemos que la vida es más rica que cualquier referente en el que la queramos enmarcar.

Algunas de las fuentes que he consultado sugieren que el uso de este patrón gana en posibilidades si se le combina con la otra novedad incorporada a la sentencia switch en C# 7.0: la cláusula when, que hace posible adosar una condición a una cláusula case, como se hace desde hace mucho tiempo en Visual Basic:

    switch (p.Age)
    {
        case var n when n < 16:
            Console.WriteLine(“Can’t vote yet!”);
            break;
        default:
            Console.WriteLine(“Potential voter!”);
            break;
    }

La cláusula when se puede asociar también a los patrones de tipo. El código que sigue es una modificación del utilizado en el ejemplo de la entrega anterior, en el que ahora se contabilizan las mascotas según su tipo y las personas según su edad. Reconozco que el ejemplo no es el mejor; observe la cantidad de código que se duplica debido al hecho de que únicamente una de las ramas de una sentencia switch se ejecuta.

class MainClass
{
    static void Main(string[] args)
    {
        Process(
            new Employee("Octavio"55"Software Eng."),
            new Person("Diana"21),
            new Pet("Shelly"PetType.DOG11)
        );
    }

    static void Process(params object[] data)
    {
        int people = 0, oldPeople = 0;
        int pets =  0, dogs = 0, cats = 0;
        foreach (var obj in data)
        {
            switch (obj)
            {
                case null:
                    break;

                case Pet pet when pet.Type == PetType.DOG:
                    Console.WriteLine($"{pet.Name} [{pet.Type}]");
                    pets++;
                    dogs++;
                    break;
                case Pet pet when pet.Type == PetType.CAT:
                    Console.WriteLine($"{pet.Name} [{pet.Type}]");
                    pets++;
                    cats++;
                    break;
                case Pet pet:
                    pets++;
                    break;

                case Employee e:
                    Console.WriteLine($"{e.Name} ({e.Position})");
                    people++;
                    if (e.Age > 50)
                        oldPeople++;
                    break;
                case Person p when p.Age > 50:
                    Console.WriteLine(p.Name);
                    people++;
                    oldPeople++;
                    break;
                case Person p:
                    Console.WriteLine(p.Name);
                    people++;
                    break;

                default:
                    Console.WriteLine("** Invalid input!");
                    break;
            }
        }
        Console.WriteLine($"Total {people} people, {oldPeople} of them old.");
        Console.WriteLine($"Total {pets} pets, of them {dogs} dogs and {cats} cats.");
    }
}

La adición de la cláusula when a la sentencia switch es en buena medida ortogonal con la presencia de esta cláusula en los bloques catch de manejo de excepciones (desde C# 6.0) para implementar lo que se conoce como filtrado de excepciones. Para esto sí que he encontrado algunos casos de uso práctico en la vida real; por ejemplo, cuando se necesita discriminar ciertos tipos de errores durante el acceso a una base de datos de SQL Server según su código (vea, por ejemplo, este enlace).


Referencia musical: Llueve mucho en estos días (suceso bastante muy poco común) aquí en el sur de California, así que echo mano de este viejo clásico de Albert Hammond. Que en realidad no trata tanto del clima local ni la lluvia como de lo mal que lo puedes pasar por estos lares si te toca una racha de mala suerte.