C# 8.0 - As novidades da nova versão


Hoje vou replicar informações da Microsoft referente a algumas das novidades que a versão 8.0 da linguagem C# vai oferecer.

A versão final do C# 8.0 esta prestes a sair do forno , provavelmente junto com a .NET Core 3.0. Enquanto isso vamos dar uma espiada nas novidades que esta nova versão vai trazer.
 

Lembrando que estamos atualmente no VS 2019, mas já temos a versão preview.

A seguir vou apresentar um resumo de 5 novidades do C# 8.0.

Você precisa instalar o VS 2019 e o .NET Core SDK 3.0 Preview na sua máquina para testar esses recursos do C# 8.0.

Após isso abra o VS 2019 e em Ferramentas -> Opções -> Projetos e Soluções marque a opção para usar a versão prévia do SDK .NET Core.

Agora você vai poder selecionar a versão .NET Core 3.0 na opção Aplicativo da janela de propriedades da Solução.

E na opção Compilar da janela de propriedades do projeto, vai ter que selecionar a versão do idioma como C# 8.0(beta):

Após essas configurações você pode abrir o VS 2019 e testar o C# 8.0.

1- Nullable reference types

O objetivo deste recurso e prevenir as exceções de null reference relativa a referência nula.

Na nova versão será emitido um aviso ou alerta quando um null for atribuido a um tipo de referência como uma string. Se você desejar atribuir o valor null mesmo assim pode usar um tipo de referência anulável ou nullable reference type:


string nome = null;
     Warning: Assignment of null to non-nullable reference type

string? nome = null;   OK
 

Nota: Dentro de um contexto de anotação anulável, qualquer variável de um tipo de referência é considerada como um tipo de referência não anulável. Se você quiser indicar que uma variável pode ser nula, você deve acrescentar o nome do tipo com o caractere ? para declarar a variável como um tipo de referência anulável.

Quando você for tentar usar uma referência anulável, você precisa verificar primeiro a referêcia nula.

O compilador analisa o fluxo do seu código para ver se um valor nulo pode vir a ser executado:

void MetodoTeste(string? nome)
{
    Console.WriteLine(nome.Length);       // Warning: Possível exceção e referência nula
    if (nome != null)
    {
        Console.WriteLine(nome.Length);    // Ok: Se nome for null esse código não executa
    }
}

Dessa forma o recurso agora permite que você expresse a sua intenção de referência nula, e, ele vai emitir um aviso quando você desobedecer a regra.

2- Switch Expressions

Uma instrução switch produz um valor em cada um dos seus blocos case.

O novo recurso switch expressions permitem que você use uma sintaxe de expressão mais concisa. Existem menos palavras-chave case e break reptidas e menos chaves. O código fica mais enxuto.

Se você já conhece a instrução switch pode dizer o que o seguinte trecho de código esta fazendo:

public enum Rainbow
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
}
public static RGBColor FromRainbow(Rainbow colorBand) =>
    colorBand switch
    {
        Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
        Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
        Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
        Rainbow.Green  => new RGBColor(0x00, 0xFF, 0x00),
        Rainbow.Blue   => new RGBColor(0x00, 0x00, 0xFF),
        Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
        Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
        _   => throw new ArgumentException(message: "Valor inválido", paramName: nameof(colorBand)),
};

Mas espere, aqui não estamos usando case, break ou return. Nas switch expressions essas palavras chaves não são mais necessárias.

Existem várias melhorias de sintaxe ao usar uma switch expressions :

Compare com o código abaixo onde estamos usando a instrução switch:

public static RGBColor FromRainbowClassic(Rainbow colorBand)
{
    switch (colorBand)
    {
        case Rainbow.Red:
            return new RGBColor(0xFF, 0x00, 0x00);
        case Rainbow.Orange:
            return new RGBColor(0xFF, 0x7F, 0x00);
        case Rainbow.Yellow:
            return new RGBColor(0xFF, 0xFF, 0x00);
        case Rainbow.Green:
            return new RGBColor(0x00, 0xFF, 0x00);
        case Rainbow.Blue:
            return new RGBColor(0x00, 0x00, 0xFF);
        case Rainbow.Indigo:
            return new RGBColor(0x4B, 0x00, 0x82);
        case Rainbow.Violet:
            return new RGBColor(0x94, 0x00, 0xD3);
        default:
            throw new ArgumentException(message: "Valor inválido", paramName: nameof(colorBand));
    };
}

3- Asycnchronous streams

A partir do C# 8.0, você pode criar e consumir streams de forma assíncrona. Um método que retorna um stream assíncrono possui três propriedades:

  1. É declarado com o modificador async;
  2. Retorna um IAsyncEnumerable <T>;
  3. O método contém instruções return yield para retornar elementos sucessivos no stream assíncrono;

Consumir um stream assíncrono requer que você adicione a palavra-chave await antes da palavra-chave foreach ao enumerar os elementos do fluxo, logo o método deve usar async e retornar um tipo permitido para um método assíncrono.

Normalmente, isso significa retornar um Task ou Task<TResult>. Também pode ser um ValueTask ou ValueTask <TResult>.

Um método pode consumir e produzir um stream assíncrono, o que significa que retornaria um IAsyncEnumerable<T>.

No exemplo a seguir geramos uma sequência de 0 a 19, esperando 100 ms entre a geração de cada número:

public static async System.Collections.Generic.IAsyncEnumerable<int> GeraSequencia()
{
    for (int i = 0; i < 20; i++)
    {
        await Task.Delay(100);
        yield return i;
    }
}

Para enumerar a sequência você deve usar a instrução await foreach:

await foreach (var numero in GeraSequencia())
{
    Console.WriteLine(numero);
}

4- Indices e ranges

Ranges (Intervalos) e índices fornecem uma sintaxe suscinta para especificar sub-intervalos em uma matriz, Span <T> ou ReadOnlySpan<T>.

Você pode especificar um índice a partir do final onde você define a   partir do final  usando o operador ^.

Se você está familiarizado com a notação array[2] que significa o elemento "2 desde o início".

Agora, a notação array[^2] significa o elemento "2 a partir do final".

O índice '^0' significa "o fim" ou o índice que segue o último elemento.

Você pode especificar um intervalo com o operador de intervalo:  ...

Por exemplo, 0 ..^0 especifica o intervalo inteiro do array: 0 do início até, mas não incluindo 0 do final.

Cada operando pode usar "a partir do inicio" ou "a partir do final".

Além disso, qualquer operando pode ser omitido. Os padrões são 0 para o índice inicial e ^0 para o índice final.

Vejamos um exemplo onde temos uma matriz anotada com seu índice a partir do inicio e partir do final:

var palavras = new string[]
{
                     // indice do inicio    índice do final
    "Um",          // 0                         ^9
    "dia",          // 1                         ^8
    "após",        // 2                          ^7
    "outro",       // 3                          ^6
    "dia",          // 4                          ^5
    "faz",          // 5                          ^4
    "a",             // 6                          ^3
    "grande",      // 7                          ^2
    "diferença"   // 8                          ^1
};

O índice de cada elemento reforça o conceito de "desde o início" e "do final", e os intervalos são exclusivos do final do intervalo. O "começo" de todo o array é o primeiro elemento. O "final" de todo o array está além do último elemento.

Você pode recuperar a última palavra usando o índice ^1:


Console.WriteLine($"A última palavra é {palavras[^1]}");

O código a seguir cria um subintervalo com as palavras "dia", "após" e "outro" e inclui o elemento palavras[1] até  palavras[3].

var minhasPalavras = palavras[1..4];
foreach(var p in minhasPalavras)
{
    Console.WriteLine(p);
}

Abaixo temos outros exemplos de utilização de Range:


WriteLine($"A última palavra é : {palavras[^1]}");
WriteLine($"A primeira palavra é : {palavras[^9]}");

Range intervalo = 1..9;
foreach (var p in palavras[intervalo])
{
     Console.WriteLine(p);
}
ReadLine();

5- Declaração Using

Uma declaração using é uma declaração de variável precedida pela palavra chave using. Diz ao compilador que a variável que está sendo declarada deve ser descartada no final do escopo.

Considere o seguinte código que grava um arquivo de texto:

static void EscreverNoArquivo(IEnumerable<string> linhas)
{
    using var arquivo = new System.IO.StreamWriter("Texto.txt");

    foreach (string linha in linhas)
    {
        // se linha não contém 'teste' escreve a linha no arquivo
        if (!linha.Contains("teste"))
        {
            arquivo.WriteLine(linha);
        }
    }//o arquivo é liberado aqui
}

No exemplo anterior, o arquivo é descartado quando a chave de fechamento do método é atingida. Esse é o fim do escopo no qual o arquivo é declarado.

O código anterior é equivalente ao seguinte código usando a declaração clássica de instruções:

static void EscreverNoArquivo(IEnumerable<string> linhas)
{
    using (var arquivo = new System.IO.StreamWriter("Texto.txt"))
    {
        foreach (string linha in linhas)
        {
             // se linha não contém 'teste' escreve a linha no arquivo
          if (!linha.Contains("teste"))
            {
                arquivo.WriteLine(linha);
            }
        }
     }   // arquivo é liberado aqui
}

No exemplo anterior, o arquivo é descartado quando a chave de fechamento associada à instrução using é atingida.

Em ambos os casos, o compilador gera a chamada para Dispose(). O compilador gera um erro se a expressão na instrução using não for descartável.

Para tornar mais claro vejamos o exemplo abaixo onde primeiro estamos usando a sintaxe clássica da instrução  using:

static void Main(string[] args)
{
     using (var arquivo = new FileStream("texto.txt", FileMode.Open))

     using (var leitor = new StreamReader(arquivo))
     {
         var texto = leitor.ReadToEnd();
         // faz alguma coisa
     }
}

Vamos agora aplicar o novo recurso usando as declarações using :

static void Main(string[] args)
{
     using Stream arquivo = new FileStream("texto.txt", FileMode.Open); 

     using StreamReader leitor = new StreamReader(arquivo);
     
     var texto = leitor.ReadToEnd();
     // faz alguma coisa     
}

Temos o código com o mesmo comportamento mas agora mais suscinto e enxuto.

Em outro artigo vamos continuar a apresentar os novos recursos do C# 8.0.

"Sujeitai-vos, pois, a Deus, resisti ao diabo, e ele fugirá de vós." Tiago 4:7

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

Referências:


José Carlos Macoratti