Deconstrucción en C# 7.0: cuidado con las sobrecargas!

“… You’re the reason I’m travelin’ on / Don’t think twice, it’s all right”
Bob Dylan, “Don’t think twice, it’s all right” (1963)

En nuestra entrega anterior presentamos la deconstrucción, un nuevo mecanismo que ofrece C# 7.0 para permitir desintegrar un objeto de cualquier tipo en las partes que lo componen, asignando los valores de esas partes a nuevas variables o a variables ya existentes utilizando la sintaxis incorporada al lenguaje para expresar tipos de tuplas-valor:

    var dh = new Person("Denis", new DateTime(1985, 12, 27));
    // ...
    (string name, int year, int month, int day) = dh;  // declaración
    string s;
    int y, m, d;
    (s, y, m, d) = dh;  // asignación
}

El tipo que ofrece la deconstrucción debe suministrar uno o más métodos Deconstruct con los parámetros de salida adecuados. Alternativamente, Deconstruct puede ser un método extensor (extension method), lo que hace posible utilizar el mecanismo para tipos de los cuales no disponemos del código fuente. Para que el código anterior funcione, la clase Person debe haberse definido así:

using System;
namespace ValueTuples
{
    public class Person
    {
        public string Name { get; }
        public DateTime BirthDate { get; }

        public Person(string name, DateTime birthDate)
        {
            Name = name;
            BirthDate = birthDate;
        }

        public void Deconstruct(out string name,
            out int year, out int month, out int day)
        {
            name = Name;
            year = BirthDate.Year; month = BirthDate.Month; day = BirthDate.Day;
        }
    }
}

Cuando empecé a probar esta característica, hasta aquí todo iba de maravilla. Pero entonces pensé que me gustaría poder devolver además del número del día el día de la semana, y claro, envolver toda la información sobre el día en una tupla, para poder hacer algo así:

    // Deconstrucción a varios niveles???
    (string name, _, _, (_, DayOfWeek dayOfWeek)) = dh;
    // Esto imprime: Denis was born a Friday.
    Console.WriteLine($"{name} was born a {dayOfWeek}.");
}

Cuando se me ocurrió hacer esto, probablemente estaría pensando en la unificación recursiva de Prolog (craso error :-)). La sobrecarga de Deconstruct con cuatro parámetros que permitiría una sintaxis como la anterior podría ser ésta:

    public void Deconstruct(out string name,
        out int year, out int month, 
        out (int DayNumber, DayOfWeek DayOfWeek) day)
    {
        name = Name;
        year = BirthDate.Year; month = BirthDate.Month; 
        day = (BirthDate.Day, BirthDate.DayOfWeek);
    }

Si se comenta la primera sobrecarga de Deconstruct y se incorpora ésta, todo funciona de maravilla. Pero si se activan los dos mecanismos simultáneamente, la clase Person compila correctamente (y por qué no), pero el código cliente que intenta utilizar la segunda sobrecarga es rechazado por el compilador:

The call is ambiguous between the following methods and properties:
    Person.Deconstruct(out string, out int, out int, out int) and
    Person.Deconstruct(out string, out int, out int, 
        out (int DayNumber, DayOfWeek DayOfWeek))

En este momento me quedé un poco desilusionado; esperaba que el compilador sería capaz de detectar que el patrón del lado izquierdo de la asignación utiliza una tupla en la cuarta posición, y por lo tanto la segunda sobrecarga es la única aceptable en ese caso. Buscando una respuesta rápida, planteé la pregunta en StackOverflow, y David Arno gentilmente me la respondió; en su propia respuesta me indica que ha enviado una sugerencia al equipo de desarrollo de C# porque piensa que la resolución de sobrecargas durante la deconstrucción no funciona tan finamente como podría.

Problemas como éste me hacen dudar de si las últimas novedades no se estarán añadiendo al lenguaje a un ritmo demasiado apresurado y sin tiempo para pensar dos veces (think twice) y estudiar todos los posibles casos extremos; de hecho, la especificación formal de C# 7.0 todavía se está escribiendo… ¡y ya salió C# 7.1! Pero tales son los tiempos ágiles que corren, y la mejor documentación de las características del lenguaje es, cada vez más, el código fuente de Roslyn en GitHub.

En cualquier caso, me ha quedado claro que es una mala idea dotar a un tipo de dos o más sobrecargas de Deconstruct con la misma cantidad de parámetros. ¡Queda advertido, estimado lector!


Referencia musical: Durante mi adolescencia, la vía casi exclusiva que tenía para escuchar pop y rock norteamericano eran las emisoras comerciales de la Florida, y Bob Dylan nunca fue especialmente popular por esos lares. Así que realmente solo empecé a conocer su obra bien pasados los treinta, gracias a mis queridos amigos de El Puerto de Santa María. “Don’t think twice…” es, sin dudas, uno de sus temas más reconocibles.

Octavio Hernandez

Desarrollador y consultor en tecnologías .NET. Microsoft C# MVP entre 2004 y 2010.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *