Una sinfonía en C#

Un pequeño aporte a la comunidad de habla hispana.

Novedades de C#7: Funciones locales

Ya hemos visto algunas de las novedaes de C#7 en este espacio y es momento de hablar de las funciones locales.

¿Qué son las funciones locales?

En pocas palabras C#7 nos da la posibilidad de declarar una función dentro de otra…si, dentro de otra, con las ventajas que esto aporta:

  • Visibilidad acotada
  • Acceso a las variables del entorno en que se declara
  • No ensuciamos el código con algo muy específico del contexto que necesitamos

Vamos a ver un ejemplo, imaginemos que tenemos una estructura tipo árbol guardada en una base de datos

image

Simple, cada elemento tiene un padre y así se arma el árbol, del lado de C# tenemos:

class Hoja
{
    public string Nombre { get; set; }
    public int Id { get; set; }
    public IList Hijos { get; set; }
    public Hoja Padre { get; set; }
}

Básicamente tiene el nombre, id, el id del padre y el listado de hijos, lo que queremos hacer es armar un árbol poniendo los hijos, lo natural sería hacer algo recursivo.

void BuscarHijos(Hoja hoja, IEnumerable hojas)
{
    foreach (var hijo in hojas.Where(i => i.Padre.Id == hoja.Id))
    {
        hoja.Hijos.Add(hijo);
        BuscarHijos(hijo, hojas);
    }
}

public IEnumerable Generar()
{
    var hojas = Hoja.GetAll();
    var lista = new List();

    foreach (var item in hojas.Where(i => i.Padre == null)) // buscamos los nodos raíz
    {
        lista.Add(item);
        BuscarHijos(item, hojas);
    }

    return lista;
}

Entonces, primero buscamos aquellas hojas que no tiene padres (son las raices) y después iteramos esa colección y buscamos las hojas que tienen como padre al nodo raíz actual, usamos una función recursiva para buscar hijos de hijos, esto debería funcionar.

Usando funciones locales.

El mismo código con funciones locales se pude hacer así:

var lista = new List();

foreach (var item in hojas.Where(i=>i.Padre == null))
{
    lista.Add(item);
    BuscarHijos(item);

    void BuscarHijos(Hoja hoja)
    {
        foreach (var hijo in hojas.Where(i => i.Padre.Id == hoja.Id))
        {
            hoja.Hijos.Add(hijo);
            BuscarHijos(hijo);
        }
    };
}

Como vemos declaramos la función BuscarHijos no solo dentro del método sino dentro del bucle y acotamos bien su impacto, además tenemos visibilidad de la lista de hojas y de la misma función BuscarHijo, super cool

Hay más detalles sobre las funciones locales en este link, y la propuesta original de la funcionalidad acá.

Nos leemos.

Novedades de C#7: Ref Returns y Ref locals

Ya hablamos de algunas de las novedaes de C#7 como literales binarios, variables de salida y pattern matching, seguimos con las novedades del C# en este caso vamos a ver ref Returns y ref locals.

Una historia interesante

Si hay una cosa que me gusta mucho de estas features es que nacieron como comunidad, una propuesta en Github que se discute, se analiza, se propone y termina llegando a C#, algo impensado 5 años atrás, esto es muy interesante pensando en el futuro y la evolución del lenguaje.

Ahora sí, qué son ref returns y ref locals?

Desde siempre en C# podemos pasar parámetros por referencia así:

int val = 1;
Method(ref val);
Console.WriteLine(val);

Entonces el método “Method” modifica el valor de la variable val que le pasamos, es decir, utiliza el mismo espacio de memoria.

ok, sin embargo si el método retornase un valor este sería una copia, ahí entran los ref return

static ref Persona HacerAlgo(ref Persona p)
{
    return ref p;
}

Más allá de que el método no hace nada lo interesante es que nos retorna el mismo objeto (no una copia) que le pasamos, entonces dentro del método modificamos el mismo objeto que se pasó como referencia.

Para qué es útil esto?

En el caso de manipular variables muy grandes usar siempre la misma referencia puede tener un impacto positivo enorme ya que no es necesario hacer copias y reservar más memoria.

ref locals

Otra feature asociada a este es la posibilidad de declarar variables ref dentro del método para utilizarla como valores de retono ref buscardo nuevamente mejor performance.

ref Persona local = ref p;

Nos leemos!

Novedades de C#7: Literales binarios.

Ya hablamos hace un tiempo de las novedaes de C#7 que vendrán como parte de Visual Studio 2017, siguiendo con lo mismo vamos a ver una simple pero efectiva, los literales binarios.

Literales

Hasta hoy podíamos declarar literales de números enteros por ejemplo, así:

int[] numeros  = { 1, 2, 3, 4, 5, 6 };

Y esto está muy bien, tenemos enteros a la izquiera y a la derecha

Literales binarios

Aquí la novedad, con C#7 podemos declarar este mismo array pero con binarios, algo así:

int[] numeros  = { 0b1, 0b10, 0b11, 0b100, 0b101, 0b110 };

Nótese el prefijo 0b, con eso indicamos que se trata de binarios, si corremos la aplicación vemos que en definitiva tenemos el mismo resultado.

image

En definitiva terminamos declarando enteros.

Separadores

Una última cosa con respecto a los literales (sobre todo los binarios) en ocasiones puden convertirse en algo poco legible, como esto:

int[] numeros  = { 0b1000000000000 };

Bien, digamos que no es tan simple darse cuenta de cuántos ceros hay, por eso tenemos la posibilidad de hacer esto

int[] numeros  = { 0b10_0000_0000_000 };

Que es justo lo mismo, es decir, usamos un guión bajo como separador, de hecho podemos poner todos los que queramos

int[] numeros  = { 0b10_____0000_0000_000 };

Y funciona igual, no solo eso, sino que podemos usarlo en otro tipo de literales

int[] numeros  = { 4_096 };

Interesante, nos leemos.

Novedades de C#7

Vamos a ver un resumen de algunas de la novedades que trae C#7.

Variable de salida:

Hasta hoy si queríamos usar una variable de salida (out) por ejemplo en un TryParse, hacíamos así:

int i = 0;
string s = "10";

if(int.TryParse(s, out i)) {
  //hacer algo
}

No está mal, una vez confirmado el if podemos usar el valor en i, con C#7 podemos hacerlo de una manera más resumida

string s = "10";

if(int.TryParse(s, out int i)) {
  //hacer algo
}

Nos ahorramos la declaración de i por fuera del if, sin embargo dentro del if tenemos acceso a i.

Pattern matching

Esta característica es mucho más potente, nos permite hacer varias cosas, vamos a ver una de las que más me llama la atención, usar tipos en los switch

switch(shape)
{
    case Circle c:
        WriteLine($"circle with radius {c.Radius}");
        break;
    case Rectangle s when (s.Length == s.Height):
        WriteLine($"{s.Length} x {s.Height} square");
        break;
    case Rectangle r:
        WriteLine($"{r.Length} x {r.Height} rectangle");
        break;
    default:
        WriteLine("");
        break;
    case null:
        throw new ArgumentNullException(nameof(shape));
}

Como vemos podemos hacer un switch y poner tipos en los case, incluso con condiciones dentro de los mismos, obviamete las condiciones se evalúan solo si el tipo coincide, genial.

Notemos que incluso se evalúa null como un tipo, no nos confundamos a pesar de que el default está antes del null siempre se evalúa defaul en último lugar.

Sin llegar a usar un case podemos hacer otras cosas interesantes como ésta

if ((shape is Rectangle s) && s.Width == s.Length)  

Básicamente evaluamos si shape es del tipo Rectangle y si lo es asignamos el resultado de la igualación de tipos a la variable s, nuevamente, el scope de la variable es el if. Muy bueno.

Y hay más

Hay otras funcionalidades que se agregan a C# y otras que están planificadas, hay una buena lista acá.

Seguramente iremos viendo más en la medida que estén disponibles.

Nos leemos.