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.
Así que he decidido coger prestado este sistema. La idea es que:
-
en el eje vertical encontraremos cómo de buena resulta la característica para el lenguaje
-
y en el horizontal cómo de loco nos parece que es implementarla como la han implementado
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…
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:
-
El orden de lo case importa.
-
La cláusula default siempre será evaluada la última. Aunque esté rodeada de case’s.
-
El case null del final sí que es posible que se ejecute.
-
La variable que se declara dentro de un case solo está disponible en el scope de ese case, no en el de todo el switch.
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:
-
Usar parámetros “out” (pero no va con async/await)
-
Usar el objeto System.Tuple<…> como valor a devolver por la función (meh!)
-
Usar métodos anónimos (no sirve para static)
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):
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”?