Introducción a las nuevas características de C# 6

La versión 6 del lenguaje C# sigue evolucionando el lenguaje para tener menos código repetitivo, mayor claridad y más coherencia. Una sintaxis de inicialización más limpia, la capacidad de usar await en bloques catch/finally y el operador condicional null ? son especialmente útiles.

Nota:

Para obtener información sobre la versión más reciente del lenguaje C#, la versión 7, consulte el artículo Novedades de C# 7.0.

En este documento, se presentan las nuevas características de C# 6. Es totalmente compatible con el compilador mono y los desarrolladores pueden empezar a usar las nuevas características en todas las plataformas de destino de Xamarin.

Vídeo sobre las novedades de C# 6

Uso de C# 6

El compilador de C# 6 se usa en todas las versiones recientes de Visual Studio para Mac. Quienes usan compiladores de la línea de comandos deben confirmar que mcs --version devuelve la versión 4.0 o superior. Los usuarios de Visual Studio para Mac pueden comprobar si tienen Instalado Mono 4 (o posterior) consultando Acerca de Visual Studio para Mac > Visual Studio para Mac > Mostrar detalles.

Menos código repetitivo

using static

Las enumeraciones y ciertas clases, como System.Math, son principalmente titulares de valores estáticos y funciones. En C# 6, puede importar todos los miembros estáticos de un tipo con una sola instrucción using static. Compare una función trigonométrica típica en C# 5 y C# 6:

// Classic C#
class MyClass
{
    public static Tuple<double,double> SolarAngleOld(double latitude, double declination, double hourAngle)
    {
        var tmp = Math.Sin (latitude) * Math.Sin (declination) + Math.Cos (latitude) * Math.Cos (declination) * Math.Cos (hourAngle);
        return Tuple.Create (Math.Asin (tmp), Math.Acos (tmp));
    }
}

// C# 6
using static System.Math;

class MyClass
{
    public static Tuple<double, double> SolarAngleNew(double latitude, double declination, double hourAngle)
    {
        var tmp = Asin (latitude) * Sin (declination) + Cos (latitude) * Cos (declination) * Cos (hourAngle);
        return Tuple.Create (Asin (tmp), Acos (tmp));
    }
}

using static no hace que los campos const públicos, como Math.PI y Math.E, sean accesibles directamente:

for (var angle = 0.0; angle <= Math.PI * 2.0; angle += Math.PI / 8) ... 
//PI is const, not static, so requires Math.PI

Instrucción using static con métodos de extensión

La instrucción using static funciona un poco diferente con métodos de extensión. Aunque los métodos de extensión se escriben mediante el uso de static, no tienen sentido sin una instancia en la que operar. Por lo tanto, cuando se utiliza using static con un tipo que define métodos de extensión, los métodos de extensión estarán disponibles en su tipo de destino (el tipo this del método). Por ejemplo, se puede usar using static System.Linq.Enumerable para ampliar la API de los objetos IEnumerable<T> sin incluir todos los tipos de LINQ:

using static System.Linq.Enumerable;
using static System.String;

class Program
{
    static void Main()
    {
        var values = new int[] { 1, 2, 3, 4 };
        var evenValues = values.Where (i => i % 2 == 0);
        System.Console.WriteLine (Join(",", evenValues));
    }
}

En el ejemplo anterior, se muestra la diferencia en el comportamiento: el método de extensión Enumerable.Where está asociado a la matriz, mientras que se puede llamar al método estático String.Join sin hacer referencia al tipo String.

Expresiones nameof

A veces, se desea hacer referencia al nombre que se ha dado a una variable o campo. En C# 6, nameof(someVariableOrFieldOrType) devolverá la cadena "someVariableOrFieldOrType". Por ejemplo, al generar una excepción ArgumentException, es muy probable que quiera nombrar qué argumento no es válido:

throw new ArgumentException ("Problem with " + nameof(myInvalidArgument))

La principal ventaja de las expresiones nameof es que tienen comprobación de tipos y son compatibles con la refactorización con tecnología de herramientas. La comprobación de tipos de las expresiones nameof es especialmente bienvenida en situaciones en las que se usa string para asociar los tipos dinámicamente. Por ejemplo, en iOS se usa string para especificar el tipo usado para crear los prototipos de objetos UITableViewCell en un elemento UITableView. nameof puede garantizar que esta asociación no falle debido a un error ortográfico o a una refactorización descuidada:

public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
    var cell = tableView.DequeueReusableCell (nameof(CellTypeA), indexPath);
    cell.TextLabel.Text = objects [indexPath.Row].ToString ();
    return cell;
}

Aunque puede pasar un nombre completo a nameof, solo se devuelve el elemento final (después del último .). Por ejemplo, puede agregar un enlace de datos en Xamarin.Forms:

var myReactiveInstance = new ReactiveType ();
var myLabelOld.BindingContext = myReactiveInstance;
var myLabelNew.BindingContext = myReactiveInstance;
var myLabelOld.SetBinding (Label.TextProperty, "StringField");
var myLabelNew.SetBinding (Label.TextProperty, nameof(ReactiveType.StringField));

Las dos llamadas a SetBinding pasan valores idénticos: nameof(ReactiveType.StringField) es "StringField", no "ReactiveType.StringField" como se podría esperar inicialmente.

Operador condicional NULL

Las actualizaciones anteriores de C# presentaron los conceptos de tipos que aceptan valores NULL y el operador de fusión de NULL ?? para reducir la cantidad de código repetitivo al controlar valores que aceptan valores NULL. C# 6 continúa este tema con el "operador condicional null" ?.. Cuando se usa en un objeto del lado derecho de una expresión, el operador condicional null devuelve el valor del miembro si el objeto no es null y null en caso contrario:

var ss = new string[] { "Foo", null };
var length0 = ss [0]?.Length; // 3
var length1 = ss [1]?.Length; // null
var lengths = ss.Select (s => s?.Length ?? 0); //[3, 0]

(Se infiere que tanto length0 como length1 son de tipo int?)

La última línea del ejemplo anterior muestra el operador condicional null ? en combinación con el operador de fusión de NULL ??. El nuevo operador condicional null de C# 6 devuelve null en el segundo elemento de la matriz, en cuyo punto el operador de fusión de NULL inicia y proporciona un 0 a la matriz lengths (por supuesto, que sea apropiado o no es específico del problema).

El operador condicional null debería reducir enormemente la cantidad de comprobación de null repetitiva necesaria en muchas y muchas aplicaciones.

Hay algunas limitaciones en el operador condicional null debido a las ambigüedades. No puede seguir inmediatamente un operador ? con una lista de argumentos entre paréntesis, como podría esperar hacer con un delegado:

SomeDelegate?("Some Argument") // Not allowed

Sin embargo, se puede usar Invoke para separar el operador ? de la lista de argumentos y sigue siendo una mejora significativa sobre un bloque de comprobación de null repetitivo:

public event EventHandler HandoffOccurred;
public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
    HandoffOccurred?.Invoke (this, userActivity.UserInfo);
    return true;
}

Interpolación de cadenas

La función String.Format ha usado tradicionalmente índices como marcadores de posición en la cadena de formato, por ejemplo, String.Format("Expected: {0} Received: {1}.", expected, received). Por supuesto, agregar un nuevo valor siempre ha implicado la molesta tarea de contar los argumentos, volver a numerar los marcadores de posición e insertar el nuevo argumento en la secuencia correcta de la lista de argumentos.

La nueva característica de interpolación de cadenas de C# 6 mejora considerablemente en String.Format. Ahora, puede nombrar directamente variables en una cadena con el prefijo $. Por ejemplo:

$"Expected: {expected} Received: {received}."

Por supuesto, las variables se comprueban y una variable mal escrita o no disponible provocará un error del compilador.

Los marcadores de posición no tienen por qué ser simples variables, pueden ser cualquier expresión. Dentro de estos marcadores de posición, puede usar comillas sin escapar dichas comillas. Por ejemplo, observe "s" a continuación:

var s = $"Timestamp: {DateTime.Now.ToString ("s", System.Globalization.CultureInfo.InvariantCulture )}"

La interpolación de cadenas admite la sintaxis de formato y alineación de String.Format. Al igual que antes escribiría {index, alignment:format}, en C# 6 escribirá {placeholder, alignment:format}:

using static System.Linq.Enumerable;
using System;

class Program
{
    static void Main ()
    {
        var values = new int[] { 1, 2, 3, 4, 12, 123456 };
        foreach (var s in values.Select (i => $"The value is { i,10:N2}.")) {
            Console.WriteLine (s);
        }
    Console.WriteLine ($"Minimum is { values.Min(i => i):N2}.");
    }
}

genera:

The value is       1.00.
The value is       2.00.
The value is       3.00.
The value is       4.00.
The value is      12.00.
The value is 123,456.00.
Minimum is 1.00.

La interpolación de cadenas es azúcar sintáctico para String.Format: no se puede usar con literales de cadena @"" y no es compatible con const, incluso si no se usan marcadores de posición:

const string s = $"Foo"; //Error : const requires value

En el caso habitual de crear argumentos de función con interpolación de cadenas, hay que tener cuidado con los problemas de escape, codificación y referencia cultural. Las consultas SQL y URL son, por supuesto, fundamentales para la limpieza. Al igual que con String.Format, la interpolación de cadenas usa CultureInfo.CurrentCulture. El uso de CultureInfo.InvariantCulture es un poco más prolijo:

Thread.CurrentThread.CurrentCulture  = new CultureInfo ("de");
Console.WriteLine ($"Today is: {DateTime.Now}"); //"21.05.2015 13:52:51"
Console.WriteLine ($"Today is: {DateTime.Now.ToString(CultureInfo.InvariantCulture)}"); //"05/21/2015 13:52:51"

Inicialización

C# 6 proporciona una serie de formas concisas de especificar propiedades, campos y miembros.

Inicialización de propiedades automáticas

Las propiedades automáticas ahora se pueden inicializar de la misma manera concisa que los campos. Las propiedades automáticas inmutables se pueden escribir solo con un captador:

class ToDo
{
    public DateTime Due { get; set; } = DateTime.Now.AddDays(1);
    public DateTime Created { get; } = DateTime.Now;

En el constructor, puede establecer el valor de una propiedad automática de solo captador:

class ToDo
{
    public DateTime Due { get; set; } = DateTime.Now.AddDays(1);
    public DateTime Created { get; } = DateTime.Now;
    public string Description { get; }

    public ToDo (string description)
    {
        this.Description = description; //Can assign (only in constructor!)
    }

Esta inicialización de propiedades automáticas es una característica general de ahorro de espacio y una ventaja para los desarrolladores que desean resaltar la inmutabilidad en sus objetos.

Inicializadores de índice

C# 6 presenta los inicializadores de índice, que permiten establecer la clave y el valor en los tipos que tienen un indizador. Normalmente, esto es para las estructuras de datos de estilo Dictionary:

partial void ActivateHandoffClicked (WatchKit.WKInterfaceButton sender)
{
    var userInfo = new NSMutableDictionary {
        ["Created"] = NSDate.Now,
        ["Due"] = NSDate.Now.AddSeconds(60 * 60 * 24),
        ["Task"] = Description
    };
    UpdateUserActivity ("com.xamarin.ToDo.edit", userInfo, null);
    statusLabel.SetText ("Check phone");
}

Miembros de función con cuerpos de expresión

Las funciones lambda tienen varias ventajas, una de las cuales es simplemente que ahorran espacio. De forma similar, los miembros de clase con cuerpos de expresión permiten expresar funciones pequeñas de forma un poco más concisa de lo que era posible en versiones anteriores a C# 6.

Los miembros de función con cuerpos de expresión usan la sintaxis de flecha de lambda en lugar de la sintaxis de bloque tradicional:

public override string ToString () => $"{FirstName} {LastName}";

Observe que la sintaxis de flecha de lambda no usa un elemento return explícito. Para las funciones que devuelven void, la expresión también debe ser una instrucción:

public void Log(string message) => System.Console.WriteLine($"{DateTime.Now.ToString ("s", System.Globalization.CultureInfo.InvariantCulture )}: {message}");

Los miembros con cuerpos de expresión siguen estando sujetos a la regla por la que se admite async para los métodos, pero no para las propiedades:

//A method, so async is valid
public async Task DelayInSeconds(int seconds) => await Task.Delay(seconds * 1000);
//The following property will not compile
public async Task<int> LeisureHours => await Task.FromResult<char> (DateTime.Now.DayOfWeek.ToString().First()) == 'S' ? 16 : 5;

Excepciones

No hay duda de que es difícil controlar bien las excepciones. Las nuevas características de C# 6 hacen que el control de excepciones sea más flexible y coherente.

Filtros de excepciones

Por definición, las excepciones se producen en circunstancias inusuales y puede ser muy difícil razonar y codificar sobre todas las formas en las que se podría producir una excepción de un tipo determinado. C# 6 presenta la capacidad de proteger un controlador de ejecución con un filtro evaluado en tiempo de ejecución. Para ello, agregue un patrón when (bool) después de la declaración catch(ExceptionType) normal. En lo siguiente, un filtro distingue un error de análisis relacionado con el parámetro date en lugar de otros errores de análisis.

public void ExceptionFilters(string aFloat, string date, string anInt)
{
    try
    {
        var f = Double.Parse(aFloat);
        var d = DateTime.Parse(date);
        var n = Int32.Parse(anInt);
    } catch (FormatException e) when (e.Message.IndexOf("DateTime") > -1) {
        Console.WriteLine ($"Problem parsing \"{nameof(date)}\" argument");
    } catch (FormatException x) {
        Console.WriteLine ("Problem parsing some other argument");
    }
}

await en catch...finally…

Las funcionalidades async presentadas en C# 5 han sido un modificador importante para el lenguaje. En C# 5, no se permitía await en bloques catch y finally, una molestia dado el valor de la funcionalidad async/await. C# 6 elimina esta limitación y permite esperar por los resultados asincrónicos de forma coherente en el programa, como se muestra en el siguiente fragmento de código:

async void SomeMethod()
{
    try {
        //...etc...
    } catch (Exception x) {
        var diagnosticData = await GenerateDiagnosticsAsync (x);
        Logger.log (diagnosticData);
    } finally {
        await someObject.FinalizeAsync ();
    }
}

Resumen

El lenguaje C# sigue evolucionando para que los desarrolladores sean más productivos, al tiempo que se promueven procedimientos recomendados y herramientas de apoyo. Este documento ha proporcionado información general sobre las nuevas características del lenguaje en C# 6 y ha mostrado brevemente cómo se usan.