Novedades de c# 7

16 Nov 2016 · 12 mins. de lectura

La nueva versión de c# se acerca. Será la número 7 ya. C# 7 lo llaman. Aunque posiblemente le acompañará una versión de .Net Framework 4.X.X. Ó quizá una dotNet Core 1.X. De cualquier forma no podemos ignorarlo. Pero no es mi intención realizar un aburrido recorrido sobre sus novedosas características. El objetivo del post es realizar un aburrido recorrido sobre sus novedosas características, y ponerles nota.

El problema es que puede resultar un poco simplista usar el manido sistema de una escala de valores del 1 al 10. Las nuevas características de c# se merecen un sistema de puntuación que nos aporte más granularidad del detalle…

Recuerdo que en un capitulo de la serie How I Met Your Mother nuestro estimado Barney Stinson exponía un sistema de calificación llamado escala sexi-loca.

escala sexy loca

Así que he decidido coger prestado este sistema. La idea es que:

Determinaremos que toda puntuación que se encuentra por encima de la diagonal Vicky Mendoza es una buena característica. Y lo que se encuentre por debajo… ejem…

escala useful-crazy

Nota: la diagonal Vicky Mendoza es la recta formada por la función x = y.

Variables “out”

Si alguna vez hemos trabajado con este tipo de variables sabremos lo engorroso que resulta tenerlas que declarar antes de llamar a la función que las asigna. En c# 7 esto deja de ser un problema. Ahora las podemos declarar inline. Además, después de declararlas de esta forma, están disponibles para su uso en el scope principal:

public void PrintCoordinates(Point p)
{
    p.GetCoordinates(out int x, out int y);
    Console.WriteLine($"({x}, {y})");
}

Y como además conocemos el tipo que se va a devolver, ahora se permite el uso de “var” en sus declaraciones:

public void PrintCoordinates(Point p)
{
    p.GetCoordinates(out var x, out var y);
    Console.WriteLine($"({x}, {y})");
}

Valoración:

Useful = 9

Crazy = 2

Pattern matching

No sé si alguno habrá trabajado con F# alguna vez. Si lo has hecho, te sonará esta funcionalidad. Si no, estás de suerte: ahora ya nada más que tu ignorancia impedirá que programes usando el paradigma funcional.

Pattern matching es un patrón de programación que nos ayuda a determinar si un objeto cumple unas características determindas. Algo así como un montón de if’s muy potentes, o un super switch.

En un “if”

La mejor forma de ver qué es pattern matching es un ejemplo:

public void PrintStars(object o)
{
    if (o is null) return;     // determina si un objeto es nulo
    if (!(o is int i)) return; // determina si un objeto es un int y lo asigna a "i"
    WriteLine(new string('*', i)); // note que el scope de "i" no es solo el "if"
}

Una forma de usarlo que parece un poco magia negra sería al transformar un “object” en un “int”:

if (o is int i || (o is string s && int.TryParse(s, out i)) { /* use i */ }

En un “switch”

Siempre me ha hecho gracia cuando hablas sobre que “switch is evil”, que algún iluminado salta con un “en mi código nunca uso ‘switch’”. Pero luego vas a su código y tiene una cantidad de if’s encadenados que da miedo. Un if es como un switch. Por lo que en este tipo de cláusulas, también podremos usar pattern matching:

switch(shape)
{
    case Circle c: // determina si es de tipo 'Circle' y lo asigna a 'c'
        WriteLine($"circle with radius {c.Radius}");
        break;
    case Rectangle s when (s.Length == s.Height): // determina si es un cuadrado
        WriteLine($"{s.Length} x {s.Height} square");
        break;
    case Rectangle r: // si es un rectángulo
        WriteLine($"{r.Length} x {r.Height} rectangle");
        break;
    default: // si no tenemos ni pajolera idea de qué es
        WriteLine("<unknown shape>");
        break;
    case null: // o si es 'null'
        throw new ArgumentNullException(nameof(shape));
}

Aquí tendremos que tener en cuenta varias cosas que son importantes:

Valoración:

Useful = 8

Crazy = 7

Tuplas

Cuando necesitas que una función devuelva más de una variable y te parece que eso del encapsulamiento de la programación orientada a objetos es una pamplina, solo puedes hacer alguna ñapa:

Pero tranquilo. C# 7 trae la solución para todos aquellos programadores que piensan que la cohesión es una fuerza física.

Las tuplas están aquí y han venido para quedarse. Esto es una forma más o menos elegante de declarar objetos anónimos al vuelo sin necesidad de definir nombres, pero sí tipos. Un ejemplo sería si quisiéramos que una función nos devolviera 3 strings:

(string, string, string) LookupName(long id) // devuelce una tupla formada por 3 strings
{
    ... // realizamos nuestra movida con las variables first, middle y last
    return (first, middle, last); // y devolvemos la tupla de 3 strings
}

La eficiencia de esto es incuestionable. El saber qué demonios devuelve la función es otra cosa…

Vamos ahora a consumir esta función:

var names = LookupName(id);
Console.WriteLine($"found {names.Item1} {names.Item3}.");

Como podéis ver solo hace falta que llamemos a las propiedades desde Item1 hasta ItemN de nuestra tupla. Está claro que este código puede confundir más que ayudar. Así que también se permite poner nombre a los diferentes objetos que devolvemos en una tupla (y menos mal):

(string first, string middle, string last) LookupName(long id) // tuplas con nombres de elementos

Para devolver una tupla con nombres, se nos permite usarlos en su creación:

    return (first: first, middle: middle, last: last); // creando una tupla usando los nombres

De tal forma que al llamarla, el código resultante será más intuitivo:

var names = LookupName(id);
WriteLine($"found {names.first} {names.last}.");

Una tupla es para el sistema un tipo de valor. Sus elementos son públicos y mutables. Además, si comparamos dos tuplas con los mismos elementos, podemos determinar si son iguales o no.

Valoración:

Useful = 7

Crazy = 7

Deconstruction

Otra forma de utilizar tuplas es la deconstrucción. Esto es una sintaxis que nos permite declarar variables individuales a las que le asignamos las diferentes propiedades de una tupla. Un ejemplo:

(string first, string middle, string last) = LookupName(id1); // deconstrucción por constructor
Console.WriteLine($"found {first} {last}."); // podemos acceder a las variables

En la deconstrucción podemos usar también tipo inferido “var”:

(var first, var middle, var last) = LookupName(id1); // var inside

O incluso abreviarlo:

var (first, middle, last) = LookupName(id1); // var outside

También se puede deconstruir una tupla usando variables ya existentes:

string first, middle, last;
(first, middle, last) = LookupName(id2); // deconstrucción con asignación

Pero cuidado. Una deconstrucción no es solo para tuplas. Vale para cualquier tipo de objeto que tengamos. Solo tenemos que crear un método “Deconstruct” que pase como parámetros de salida “out” las variables que queremos deconstruir. La firma del método podría ser esta:

public void Deconstruct(out T1 x1, ..., out Tn xn) { ... }

La parte graciosa de todo esto es que no hay herencia de ningún tipo. Cada objeto tendrá que implementar su propio método con diferentes parámetros de salida. Así que por ahora no esperéis un “IDeconstructable” ni nada por el estilo que le dé algo de consistencia a nuestro código.

Un ejemplo de uso de esta feature:

class Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y) { X = x; Y = y; }
    public void Deconstruct(out int x, out int y) { x = X; y = Y; }
}

(var myX, var myY) = GetPoint(); // esto llama a Deconstruct(out myX, out myY);

Ya estamos esperando un analyzer de roslyn que compruebe que tenemos un constructor y una función “Deconstruct” con los mismos parámetros para que tenga sentido todo esto.

Valoración:

Useful = 7

Crazy = 8

Wildcards

El nuevo sistema de wildcards nos permite usar el símbolo asterisco para no tener que definir algo que no queremos. Por ejemplo, si tenemos una variable “out” que no necesitamos, podemos usarlo:

p.GetCoordinates(out int x, out *); // solo me preocupa la 'x'

O en el caso de usar deconstrucción, también podemos usarlo:

(var myX, *) = GetPoint(); // I only care about myX

Valoración:

Useful = 4

Crazy = 8

Funciones locales

Si eres de los que echa de menos la forma de programar de javascript. Si te mola un montón tener funciones anidadas unas dentro de otras. Si te piensas que el espagueti code se hace solo con if’s. Ahora tenemos una de las novedades de c# 7: funciones locales.

¿Y esto qué es lo qué es? Te preguntarás… Pues muy fácil: declarar funciones en medio de una función. Así:

public int Fibonacci(int x)
{
    if (x < 0) throw new ArgumentException("Less negativity please!", nameof(x));
    return Fib(x).current;

    (int current, int previous) Fib(int i)
    {
        if (i == 0) return (1, 0);
        var (p, pp) = Fib(i - 1);
        return (p + pp, p);
    }
}

Con hoisting. Una funcionalidad muy útil cuando tienes una función recursiva y otra que realiza la llamada de la función recursiva :).

Además de estar en el mismo contexto de una función, prevendrá a otros objetos llamar a este método sin comprobar que efectivamente el número x es mayor de 1.

Valoración:

Useful = 2

Crazy = 9

Literales

¿Nunca has tenido en tu código un magic number que era muy difícil de leer por ser muy largo? C# 7 tiene la solución. Ahora puedes separar los dígitos literales con ‘_’. Y este símbolo no alterará su valor. Por ejemplo:

var d = 123_456;
var x = 0xAB_CD_EF;

¿Cómo hemos podido vivir sin esta funcionalidad antes? Podrá parecer una tontería, pero si os contamos que también se han añadido literales binarios, entonces empieza a cobrar algo de sentido la posibilidad de añadir legibilidad a este tipo de cifras:

var b = 0b1010_1011_1100_1101_1110_1111;

Si no trabajas directamente en binario, es porque no quieres.

Valoración:

Useful = 3

Crazy = 8

Ref returns

Si actualmente te ves obligado a devolver una referencia de un valor. Si estás aficionado al uso de “ref”. Ahora puedes devolver una referencia directamente con una función añadiendo “ref” delante del tipo:

public ref int Find(int number, int[] numbers)
{
    for (int i = 0; i < numbers.Length; i++)
    {
        if (numbers[i] == number)
        {
            return ref numbers[i]; // devuelve la referencia a la posición del array no el valor
        }
    }

    throw new IndexOutOfRangeException($"{nameof(number)} not found");
}

int[] array = { 1, 15, -39, 0, 7, 14, -12 };
ref int place = ref Find(7, array); // devuelve la referencia a la posición donde está el 7
place = 9; // cambia el valor de 7 por 9
WriteLine(array[4]); // escribe en pantalla el 9

Y es que he estado pensando mucho acerca de esta funcionalidad. Se parece mucho a usar punteros. Quizá sea lo mismo. Podría ser que no haya punteros en c# 7. Y tendría sentido que las wildcards, al usar el caracter que usan los punteros, fueran las culpables. ¡Malditas!

Valoración:

Useful = 5

Crazy = 2

Más definiciones inline

En c# 6 se añadió la definición de métodos inline, una funcionalidad que en c# 7 se extiende. Ahora podremos declarar inline todo lo que antes no se podía:

class Person
{
    private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>();
    private int id = GetId();

    public Person(string name) => names.TryAdd(id, name); // constructores
    ~Person() => names.TryRemove(id, out *);              // destructores
    public string Name
    {
        get => names[id];                                 // getters
        set => names[id] = value;                         // setters
    }
}

Y lo que es mejor: es una funcionalidad que viene de la comunidad. ¡Viva el open source!

Valoración:

Useful = 9

Crazy = 1

Throw en expresiones

Otra de las nuevas ventajas de c# 7 es poder lanzar excepciones dentro de expresiones. Suena un poco raro, pero creo que es la funcionalidad que cierra el círculo de las definiciones inline:

class Person
{
    public string Name { get; }
    public Person(string name) => Name = name ?? throw new ArgumentNullException(name);
    public string GetFirstName()
    {
        var parts = Name.Split(" ");
        return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!");
    }
    public string GetLastName() => throw new NotImplementedException();
}

Valoración:

Useful = 8

Crazy = 1

Bola extra: default interface implementations

Esta es una funcionalidad que está muy en el aire. Pero se comenta que en user voice (se comenta aquí para ser exactos: https://github.com/dotnet/roslyn/issues/73) que un degenerado solicitó imitar la peor feature de java de los últimos años en c#: definir una implementación por defecto para una interfaz. Por ahora esto es lo que plantean:

interface ISomeInterface
{
  string Property { get; }

  default string Format()
  {
    return string.Format ("{0} ({1})", GetType().Name, Property);
  }
}

class SomeClass : ISomeInterface
{
  public string Property { get; set; }
}

Sin comentarios…

Valoración:

Useful = -1

Crazy = 1000

Conclusiones

Estas características no es seguro que aparezcan en c# 7. Aunque estoy seguro de que muchas de ellas sí. Actualmente hemos podido jugar con versiones preview. De cualquier forma, está muy bien que sigan trayendo novedades cuando ya han pasado 7 versiones de este lenguaje de programación orientado a objetos. Quizá por ser demasiadas versiones y no poder sacar más chicha de este paradigma se haya optado por incluir características de lenguajes funcionales.

A continuación unas gráficas (así parece que sabemos de lo que hablamos cientificamente):

chart-1

chart-2

Nota: hemos excluido las default interface implementations porque nos jodia las gráficas

Disclaimer: Todo esto pueden ser patrañas. No te lo tomes en serio. La información ha sido extraida de https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/, https://github.com/dotnet/roslyn/issues/73 y de la preview 4 de c# 7.

Y yo me pregunto: ¿Para cuando palabra clave “let”?

buy me a beer