Información general sobre las nuevas características de C# 6

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

Nota:

Para obtener información sobre la versión más reciente del lenguaje C#, 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.

Novedades del vídeo de C# 6

Uso de C# 6

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

Menos reutilizable

uso de versión estática

Las enumeraciones y determinadas clases, como , son principalmente System.Math titulares de funciones y valores estáticos. En C# 6, puede importar todos los miembros estáticos de un tipo con una sola using static instrucción. 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 , se Math.E puedan acceder directamente a ellos:

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

usar static con métodos de extensión

La using static instalación funciona de forma ligeramente diferente con los métodos de extensión. Aunque los métodos de extensión se escriben mediante , no tienen sentido static sin una instancia en la que se va a trabajar. Por lo tanto, cuando se usa con un tipo que define métodos de extensión, los métodos de extensión están disponibles en su tipo de destino using static (tipo del this método). Por ejemplo, using static System.Linq.Enumerable se puede usar para extender la API de objetos sin incorporar todos los tipos IEnumerable<T> 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 de comportamiento: el método de extensión está asociado a la matriz, mientras que se puede llamar al método estático sin hacer referencia Enumerable.WhereString.Join al tipo String .

nameof Expressions

A veces, quiere hacer referencia al nombre que ha dado a una variable o campo. En C# 6, nameof(someVariableOrFieldOrType) devolverá la cadena "someVariableOrFieldOrType" . Por ejemplo, al iniciar un , es muy probable que desee nombrar ArgumentException qué argumento no es válido:

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

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

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 , solo se devuelve el nameof último elemento (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 , no como podría esperar "StringField""ReactiveType.StringField" inicialmente.

Operador condicional NULL

Las actualizaciones anteriores de C# introdujeron los conceptos de los tipos que aceptan valores NULL y el operador de uso de uso de null para reducir la cantidad de código reutilizable al controlar los 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 de miembro si el objeto no es y, de lo nullnull 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]

(Tanto length0 como length1 se deducen que 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 null. El nuevo operador condicional null de C# 6 devuelve en el segundo elemento de la matriz, momento en el que el operador de uso de uso de null se inicia y proporciona un 0 a la matriz (independientemente de si es adecuado o no es, por supuesto, específico del nulllengths problema).

El operador condicional null debe reducir enormemente la cantidad de comprobación de valores NULL reutilizables necesaria en muchas aplicaciones.

Hay algunas limitaciones en el operador condicional null debido a ambigüedades. No puede seguir inmediatamente un elemento 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 para separar de la lista de argumentos y sigue siendo una mejora marcada con respecto a un Invoke? bloque de comprobación null reutilizable:

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 String.Format función 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 una tarea poco complicada de contar argumentos, volver a numerar marcadores de posición e insertar el nuevo argumento en la secuencia correcta en 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}."

Las variables están, por supuesto, activadas y una variable mal escrita o no disponible provocará un error del compilador.

Los marcadores de posición no necesitan ser variables simples, pueden ser cualquier expresión. Dentro de estos marcadores de posición, puede usar comillas sin escapar esas comillas. Por ejemplo, tenga en "s" cuenta en lo siguiente:

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

La interpolación de cadenas admite la sintaxis de alineación y formato de String.Format . Al igual que escribió {index, alignment:format} anteriormente, en C# 6 se escribe {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 un nivel sintáctico para : no se puede usar con literales de cadena y no es compatible con , aunque no se utilice String.Format@"" ningún marcador de const posición:

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

En el caso de uso común de la creación de argumentos de función con interpolación de cadenas, debe tener cuidado con los problemas de escape, codificación y referencia cultural. SQL y las consultas url son, por supuesto, fundamentales para sanear. Al igual que String.Format con , la interpolación de cadenas usa CultureInfo.CurrentCulture . El CultureInfo.InvariantCulture uso de es un poco más wordy:

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 varias maneras 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 solo se pueden escribir con un getter:

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 solo de getter:

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 buena ayuda para los desarrolladores que desean enfatizar la inmutabilidad en sus objetos.

Inicializadores de índice

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

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 forma de expresión

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

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

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

Observe que la sintaxis de la flecha lambda no usa un return explícito. Para las funciones que void devuelven , 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 forma de expresión siguen estando sujetos a la regla que se async admite para los métodos, pero no 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 dos maneras de hacerlo: el control de excepciones es difícil de conseguir. 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 todas las formas en que se puede 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 when (bool) patrón después de la declaración catch(ExceptionType) normal. A continuación, un filtro distingue un error de análisis relacionado con el parámetro en lugar de otros date 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... Finalmente...

Las async funcionalidades introducidas en C# 5 han sido un cambio de juego para el lenguaje. En C# 5, no se permitía en los bloques y , una molestia dado await el valor de la catchfinallyasync/await funcionalidad. C# 6 elimina esta limitación, lo que permite que los resultados asincrónicos se esperen de forma coherente a través del programa, como se muestra en el fragmento de código siguiente:

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 promueve los procedimientos recomendados y las herramientas de apoyo. En este documento se proporciona información general sobre las nuevas características del lenguaje en C# 6 y se ha mostrado brevemente cómo se usan.