As novidades do C# 6

Com o recente lançamento do Visual Studio 2015, foi lançada a versão 6 da linguagem de programação para a plataforma .NET C#.

Como neste lançamento o enfoque principal foi na nova plataforma de compiladores (“Roslyn”), os melhoramentos e adições à linguagem foram escassos mas, tal como os melhoramentos e adições das versões anteriores, tornarão a vida de quem desenvolve usando a linguagem de programação C# muito melhor.

1. Melhoramentos em auto-propriedades

As propriedades implementadas automaticamente (ou, abreviando, auto-propriedades) são propriedades não abstratas e não externas com acessores com corpo apenas com ponto e virgula.

Quando uma propriedade é implementada automaticamente, é criado um campo escondido para dar suporte à propriedade e os acessores de leitura e escrita são são implementados para, respetivamente, ler e escrever desse campo.

1.1. Inicializadores para auto-propriedades

Passa a ser possível declarar a inicialização de auto-propriedades da mesma forma que se inicializam os campos:

public class Person
{
    public string First { get; set; } = "Jane";
    public string Last { get; set; } = "Doe";
}

Com esta sintaxe, o inicializador inicializa diretamente o campo que dá suporte à propriedade sem recorre ao setter da propriedade.

Os inicializadores de propriedades são executados, tal como e juntamente com, os inicializadores de campos.

Tal como acontece com os inicializadores de campos, os inicializadores de propriedades não podem fazer referência a this porque, tal como acontece com os inicializadores dos campos, correm antes dos objetos estarem devidamente inicializados.

A implementação desta nova funcionalidade é feita usando funcionalidades tradicionais da linguagem tornado possível a utilização do código gerado em versões anteriores da plataforma .NET. Na verdade o código anterior é traduzido pelo compilador para o seguinte código C# 1:

public class Person
{
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public string k__BackingField = "Jane";
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public string k__BackingField = "Doe";

    public string First
    {
        [CompilerGenerated]
        get { return k__BackingField }
        [CompilerGenerated]
        set { k__BackingField = value; }
    }
    public string Last
    {
        [CompilerGenerated]
        get { return k__BackingField }
        [CompilerGenerated]
        set { k__BackingField = value; }
    }
}

Note-se que os campos k__BackingField e k__BackingField têm nomes que não são válidos em C#. Tal acontece para que não haja qualquer hipótese de colisão entre os atribuídos pelo programador e os nome atribuídos pelo compilador.

1.2. Auto-propriedades apenas de leitura

As auto-propriedades passam a dispensar o acessor de escrita passando, por isso, a poder ser apenas de leitura:

public class Person
{
    public string First { get; } = "Jane";
    public string Last { get; } = "Doe";
}

Neste caso, o campo gerado é declarado implicitamente como readonly (embora isto apenas tenha importância para efeitos de reflexão – reflection).

À semelhança do caso anterior, o código gerado será:

public class Person
{
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly string k__BackingField = "Jane";
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly string k__BackingField = "Doe";

    public string First
    {
        [CompilerGenerated]
        get { return k__BackingField; }
    }
    public string Last
    {
        [CompilerGenerated]
        get { return k__BackingField; }
    }
}

Tal como acontece com os campos apenas de leitura, no caso das auto-propriedades apenas de leitura é possível inicializar o seu valor no construtor:

public class Person
{
    // ...

    public Person(string first, string last)
    {
        First = first;
        Last = last;
    }
}

E, mais uma vez, o compilador gera código equivalente a C# 1:

public class Person
{
    // ...

    public Person(string first, string last)
    {
        k__BackingField = first;
        k__BackingField = last;
    }
}

2. Membros função com corpo em formato expressão

Passam a poder ser usadas expressões semelhantes às funções lambda para definir corpos de funções com apenas de uma instrução (statement) ou bloco trazendo às funções membros de tipos a mesma clareza e simplicidade.

2.1. Corpos em formato expressão em membros do tipo método

Métodos, assim como operadores definidos pelo utilzador e conversões, podem ter o seu corpo definito por uma expressão usando a “seta das lambdas”:

public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
public static Complex operator +(Complex a, Complex b) => a.Add(b);
public static implicit operator string (Person p) => p.First + " " + p.Last;

O efeito é exatamente o mesmo que se os métodos tivessem apenas uma instrução de return. Os exemplos acima são convertidos pelo compilador para:

public Point Move(int dx, int dy)
{
    return new Point(x + dx, y + dy);
}
public static Complex operator +(Complex a, Complex b)
{
    return a.Add(b);
}
public static implicit operator string (Person p)
{
    return p.First + " " + p.Last;
}

Para métodos cujo tipo de retorno seja void (ou Task para métodos assíncronos) a sintaxe da seta ainda se aplica, mas a expressão que se segue tem de ser uma instrução (à semelhança do que já acontece com as lambdas):

public void Print() => Console.WriteLine(First + " " + Last);

que será traduzido para:

public void Print()
{
    Console.WriteLine(First + " " + Last);
}

2.2. Corpos em formato expressão em membros do tipo propriedade

Os corpos do tipo expressão também podem ser usados para definir o corpo de propriedades e indexadores apenas de leitura:

public string Name => First + " " + Last;
public Person this[long id] => store.LookupPerson(id);

Note-se a ausência da palavra-chave get, que se torna implícita pela sintaxe de expressão.

Os exemplos anteriores são traduzidos pelo compilador para:

public string Name
{
    get
    {
        return First + " " + Last;
    }
}
public Person this[long id]
{
    get
    {
        return store.LookupPerson(id);
    }
}

Publicado na edição 50 (PDF) da Revista PROGRAMAR.