Visão geral dos novos recursos do C# 6

A versão 6 da linguagem C# continua evoluindo a linguagem para ter menos código clichê, maior clareza e mais consistência. Sintaxe de inicialização mais limpa, a capacidade de usar await em blocos catch/finally e o condicional nulo? operador são especialmente úteis.

Observação

Para obter informações sobre a versão mais recente da linguagem C# – versão 7 – consulte o artigo Novidades no C# 7.0

Este documento apresenta os novos recursos do C# 6. Ele é totalmente compatível com o compilador mono e os desenvolvedores podem começar a usar os novos recursos em todas as plataformas de destino do Xamarin.

Novidades no vídeo do C# 6

Usando o C# 6

O compilador C# 6 é usado em todas as versões recentes do Visual Studio para Mac. Aqueles que usam compiladores de linha de comando devem confirmar que mcs --version retorna 4.0 ou superior. Visual Studio para Mac usuários poderão marcar se tiverem o Mono 4 (ou mais recente) instalado, referindo-se a Sobre Visual Studio para Mac > Visual Studio para Mac > Mostrar Detalhes.

Less Boilerplate

usando estático

Enumerações e determinadas classes, como System.Math, são principalmente detentores de valores estáticos e funções. No C# 6, você pode importar todos os membros estáticos de um tipo com uma única instrução using static . Compare uma função trigonométrica típica em C# 5 e 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 não torna os campos públicos const , como Math.PI e Math.E, diretamente acessíveis:

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

usando estático com métodos de extensão

A using static instalação opera um pouco diferente com métodos de extensão. Embora os métodos de extensão sejam escritos usando static, eles não fazem sentido sem uma instância na qual operar. Portanto, quando using static é usado com um tipo que define métodos de extensão, os métodos de extensão ficam disponíveis em seu tipo de destino (o tipo do this método). Por exemplo, using static System.Linq.Enumerable pode ser usado para estender a API de IEnumerable<T> objetos sem trazer todos os tipos 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));
    }
}

O exemplo anterior demonstra a diferença de comportamento: o método Enumerable.Where de extensão está associado à matriz, enquanto o método String.Join estático pode ser chamado sem referência ao String tipo .

Expressões nameof

Às vezes, você deseja se referir ao nome que você deu a uma variável ou campo. No C# 6, nameof(someVariableOrFieldOrType) retornará a cadeia de caracteres "someVariableOrFieldOrType". Por exemplo, ao lançar um ArgumentException , é muito provável que você queira nomear qual argumento é inválido:

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

A principal vantagem das nameof expressões é que elas são verificadas por tipo e são compatíveis com a refatoração alimentada por ferramentas. A verificação de tipo de nameof expressões é particularmente bem-vinda em situações em que um string é usado para associar dinamicamente tipos. Por exemplo, no iOS um string é usado para especificar o tipo usado para protótipo UITableViewCell de objetos em um UITableView. nameof pode garantir que essa associação não falhe devido a uma refatoração incorreta ou desleixada:

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

Embora você possa passar um nome qualificado para nameof, somente o elemento final (após o último .) é retornado. Por exemplo, você pode adicionar uma associação de dados no 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));

As duas chamadas para SetBinding estão passando valores idênticos: nameof(ReactiveType.StringField) é "StringField", não "ReactiveType.StringField" como você poderia esperar inicialmente.

Operador condicional nulo

As atualizações anteriores do C# introduziram os conceitos de tipos anuláveis e o operador ?? de união nula para reduzir a quantidade de código clichê ao manipular valores anuláveis. O C# 6 continua esse tema com o "operador condicional nulo" ?.. Quando usado em um objeto no lado direito de uma expressão, o operador condicional nulo retornará o valor do membro se o objeto não null for e null de outra forma:

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]

(E length0length1 são inferidos para serem do tipo int?)

A última linha no exemplo anterior mostra o ? operador condicional nulo em combinação com o ?? operador de união nula. O novo operador c# 6 condicional nulo retorna null no 2º elemento na matriz, momento em que o operador de união nula entra em ação e fornece um 0 para a lengths matriz (se isso é apropriado ou não é, naturalmente, específico do problema).

O operador condicional nulo deve reduzir tremendamente a quantidade de verificação de nulo clichê necessária em muitos, muitos aplicativos.

Há algumas limitações no operador condicional nulo devido a ambiguidades. Você não pode seguir imediatamente um ? com uma lista de argumentos parênteses, como você pode esperar fazer com um delegado:

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

No entanto, Invoke pode ser usado para separar o ? da lista de argumentos e ainda é uma melhoria acentuada em relação a um nullbloco de verificação de texto clichê:

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

Interpolação de cadeia de caracteres

A String.Format função tem índices tradicionalmente usados como espaços reservados na cadeia de caracteres de formato, por exemplo, String.Format("Expected: {0} Received: {1}.", expected, received). É claro que adicionar um novo valor sempre envolveu uma tarefa irritante de contar argumentos, renumerar espaços reservados e inserir o novo argumento na sequência certa na lista de argumentos.

O novo recurso de interpolação de cadeia de caracteres do C# 6 melhora consideravelmente em .String.Format Agora, você pode nomear variáveis diretamente em uma cadeia de caracteres prefixada com um $. Por exemplo:

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

As variáveis são, naturalmente, verificadas e uma variável incorreta ou não disponível causará um erro do compilador.

Os espaços reservados não precisam ser variáveis simples, podem ser qualquer expressão. Dentro desses espaços reservados, você pode usar aspas sem escapar dessas aspas. Por exemplo, observe o "s" no seguinte:

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

A interpolação de cadeia de caracteres dá suporte à sintaxe de alinhamento e formatação de String.Format. Assim como você escreveu {index, alignment:format}anteriormente , no C# 6 você escreve {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}.");
    }
}

resulta em:

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.

A interpolação de cadeia de caracteres é açúcar sintático para String.Format: não pode ser usada com @"" literais de cadeia de caracteres e não é compatível com const, mesmo que nenhum espaço reservado seja usado:

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

No caso de uso comum da criação de argumentos de função com interpolação de cadeia de caracteres, você ainda precisa ter cuidado com problemas de escape, codificação e cultura. As consultas SQL e URL são, naturalmente, críticas para limpeza. Assim como acontece com String.Format, a interpolação de cadeia de caracteres usa o CultureInfo.CurrentCulture. Usar CultureInfo.InvariantCulture é um pouco mais 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"

Inicialização

O C# 6 fornece várias maneiras concisas de especificar propriedades, campos e membros.

Inicialização de propriedade automática

As propriedades automáticas agora podem ser inicializadas da mesma maneira concisa que os campos. As propriedades automáticas imutáveis podem ser gravadas com apenas um getter:

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

No construtor, você pode definir o valor de uma propriedade automática somente 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!)
    }

Essa inicialização de propriedades automáticas é um recurso geral de economia de espaço e um benefício para os desenvolvedores que desejam enfatizar a imutabilidade em seus objetos.

Inicializadores de índice

O C# 6 apresenta inicializadores de índice, que permitem definir a chave e o valor em tipos que têm um indexador. Normalmente, isso é para Dictionaryestruturas de dados no 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");
}

Membros da função com corpo de expressão

As funções lambda têm vários benefícios, um dos quais é simplesmente economizar espaço. Da mesma forma, os membros de classe aptos para expressão permitem que pequenas funções sejam expressas de forma um pouco mais sucinta do que era possível nas versões anteriores do C# 6.

Os membros da função com corpo de expressão usam a sintaxe de seta lambda em vez da sintaxe de bloco tradicional:

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

Observe que a sintaxe lambda-arrow não usa um explícito return. Para funções que retornam void, a expressão também deve ser uma instrução :

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

Os membros aptos para expressão ainda estão sujeitos à regra com async suporte para métodos, mas não propriedades:

//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;

Exceções

Não há duas maneiras: o tratamento de exceções é difícil de acertar. Novos recursos no C# 6 tornam o tratamento de exceções mais flexível e consistente.

Filtros de exceção

Por definição, as exceções ocorrem em circunstâncias incomuns e pode ser muito difícil raciocinar e codificar sobre todas as maneiras pelas quais uma exceção de um tipo específico pode ocorrer. O C# 6 apresenta a capacidade de proteger um manipulador de execução com um filtro avaliado por runtime. Isso é feito adicionando um when (bool) padrão após a declaração normal catch(ExceptionType) . No seguinte, um filtro distingue um erro de análise relacionado ao date parâmetro em vez de outros erros de análise.

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");
    }
}

aguardar em catch... Finalmente...

Os async recursos introduzidos no C# 5 foram um divisor de águas para a linguagem. No C# 5, await não era permitido nos catch blocos e finally , um aborrecimento dado o valor da async/await capacidade. O C# 6 remove essa limitação, permitindo que os resultados assíncronos sejam aguardados consistentemente por meio do programa, conforme mostrado no snippet a seguir:

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

Resumo

A linguagem C# continua evoluindo para tornar os desenvolvedores mais produtivos e, ao mesmo tempo, promovendo boas práticas e ferramentas de suporte. Este documento deu uma visão geral dos novos recursos de linguagem no C# 6 e demonstrou brevemente como eles são usados.