Mais recentemente, foi lançado o Visual Studio 2019 Preview 2. E com ele, alguns recursos adicionais do C # 8.0 estão prontos para você experimentá-los. Esta é principalmente uma comparação com a amostra, embora, no final, abordarei algumas outras notícias e mudanças.
Este artigo está em inglês.

Obrigado por traduzir o nosso MSP,
Lev Bulanov .
Mais padrões em mais lugares
Quando a correspondência de padrões apareceu no C # 7.0, observamos que um aumento no número de padrões em mais locais é esperado no futuro. Chegou a hora! Estamos adicionando o que chamamos de padrões recursivos, bem como uma forma mais compacta de expressões de chave chamadas (você adivinhou).
Para começar, aqui está um exemplo simples de padrões do C # 7.0:
class Point { public int X { get; } public int Y { get; } public Point(int x, int y) => (X, Y) = (x, y); public void Deconstruct(out int x, out int y) => (x, y) = (X, Y); } static string Display(object o) { switch (o) { case Point p when pX == 0 && pY == 0: return "origin"; case Point p: return $"({pX}, {pY})"; default: return "unknown"; } }
Alternar expressões
Primeiro, observe que muitas expressões de troca , de fato, não fazem muito trabalho interessante nos corpos dos casos . Geralmente, todos eles simplesmente criam um valor, atribuindo-o a uma variável ou retornando-o (como indicado acima). Em todas essas situações, o interruptor parece estar fora de lugar. Isso é semelhante a um recurso de idioma com cinquenta anos de idade.
Decidimos que era hora de adicionar um formulário de declaração de mudança . Aplica-se ao exemplo abaixo:
static string Display(object o) { return o switch { Point p when pX == 0 && pY == 0 => "origin", Point p => $"({pX}, {pY})", _ => "unknown" }; }
Há algumas coisas que foram alteradas em comparação com as instruções do switch. Vamos listá-los:
- A palavra-chave switch é "infix" entre o valor testado e a lista de {...} casos. Isso o torna mais composicional com outras expressões e também mais fácil distinguir visualmente a instrução switch.
- A palavra-chave e o símbolo do caso : foram substituídos pela seta lambda => para abreviar.
- O padrão de brevidade foi substituído pelo padrão _ reset.
- Corpos são expressões. O resultado do corpo selecionado se torna o resultado da instrução switch.
Como a expressão deve importar ou gerar uma exceção, uma expressão selecionada que termina sem uma correspondência gera uma exceção. O compilador avisará quando isso acontecer, mas não forçará o encerramento de todas as instruções de seleção com a função catch-all.
Como nosso método Display agora consiste em uma única declaração de retorno, podemos simplificá-lo para expressão:
static string Display(object o) => o switch { Point p when pX == 0 && pY == 0 => "origin", Point p => $"({pX}, {pY})", _ => "unknown" };
Quaisquer que sejam as recomendações de formatação, elas devem ser extremamente claras e concisas. A brevidade permite formatar o comutador de maneira "tabular", como descrito acima, com padrões e corpo na mesma linha e => alinhados um com o outro.
A propósito, planejamos permitir o uso de uma vírgula após o último caso, de acordo com todas as outras "listas separadas por vírgulas entre colchetes" em C #, mas isso ainda não é permitido na Visualização 2.
Propriedades do padrão
Falando em brevidade, os padrões de repente se tornam os elementos mais difíceis das expressões de escolha. Vamos fazer algo sobre isso.
Observe que a expressão de seleção usa um padrão do tipo Ponto p (duas vezes), bem como quando adicionar condições adicionais no primeiro caso .
No C # 8.0, adicionamos elementos opcionais adicionais ao tipo de padrões, o que permite que o próprio padrão vá mais fundo no valor que mapeia para o padrão. Você pode torná-lo um padrão de propriedade adicionando {...} contendo padrões aninhados, aplicando valores às propriedades ou campos disponíveis. Isso nos permite reescrever a expressão do switch da seguinte maneira:
static string Display(object o) => o switch { Point { X: 0, Y: 0 } p => "origin", Point { X: var x, Y: var y } p => $"({x}, {y})", _ => "unknown" };
Ambos os casos ainda verificam se o é Point . No primeiro caso, o padrão da constante 0 é aplicado recursivamente às propriedades X e Y da variável p , verificando se elas têm esse valor. Assim, podemos eliminar a condição when neste e em outros casos semelhantes.
No segundo caso, o padrão var é aplicado a cada um de X e Y. Lembre-se de que o padrão var no C # 7.0 sempre é bem-sucedido e simplesmente declara uma nova variável para manter o valor. Então x e y contêm valores int para pX e pY .
Nós nunca usamos p e podemos realmente pular aqui:
Point { X: 0, Y: 0 } => "origin", Point { X: var x, Y: var y } => $"({x}, {y})", _ => "unknown"
Uma coisa permanece a mesma para todos os tipos de padrões, incluindo padrões de propriedade, este é um requisito para que o valor seja diferente de zero. Isso abre a possibilidade de usar o padrão de propriedades "vazias" {} como um padrão compacto "diferente de zero". Por exemplo. podemos substituir o fallback pelos dois casos a seguir:
{} => o.ToString(), null => "null"
{} Lida com os demais objetos diferentes de zero, e null obtém zeros, portanto a opção é completa e o compilador não reclama sobre os valores ausentes.
Padrões posicionais
O padrão da propriedade não reduz o segundo caso Point. Não há necessidade de se preocupar com isso, você pode fazer ainda mais.
Observe que a classe Point possui um método Deconstruct , o chamado desconstrutor. No C # 7.0, os desconstrutores permitem "desconstruir" um valor quando atribuído, para que você possa escrever, por exemplo:
(int x, int y) = GetPoint();
O C # 7.0 não integrou a desconstrução aos padrões. Isso muda com os padrões posicionais, que são uma maneira adicional de estender os tipos de padrões no C # 8.0. Se o tipo de correspondência for um tipo de tupla ou tiver um desconstrutor, podemos usar padrões posicionais como uma maneira compacta de aplicar padrões recursivos sem precisar nomear as propriedades:
static string Display(object o) => o switch { Point(0, 0) => "origin", Point(var x, var y) => $"({x}, {y})", _ => "unknown" };
Depois de corresponder o objeto com Point , o desconstrutor é aplicado e os padrões aninhados são aplicados aos valores resultantes.
Os desconstrutores nem sempre são adequados. Eles devem ser adicionados apenas aos tipos em que fica realmente claro qual dos valores é qual. Por exemplo, para a classe Point , você pode assumir que o primeiro valor é X e o segundo é Y , portanto, a expressão da opção acima é clara e fácil de ler.
Padrões de tupla
Um caso especial muito útil de padrões posicionais é a sua aplicação às tuplas. Se a instrução switch for aplicada diretamente à expressão da tupla, podemos até omitir o conjunto adicional de parênteses, como no switch (x, y, z) em vez do switch ((x, y, z)) .
Os padrões de tupla são ótimos para testar várias entradas ao mesmo tempo. Aqui está uma implementação simples da máquina de estado:
static State ChangeState(State current, Transition transition, bool hasKey) => (current, transition) switch { (Opened, Close) => Closed, (Closed, Open) => Opened, (Closed, Lock) when hasKey => Locked, (Locked, Unlock) when hasKey => Closed, _ => throw new InvalidOperationException($"Invalid transition") };
Obviamente, poderíamos incluir hasKey na tupla em vez de usar as cláusulas when - isso é uma questão de gosto:
static State ChangeState(State current, Transition transition, bool hasKey) => (current, transition, hasKey) switch { (Opened, Close, _) => Closed, (Closed, Open, _) => Opened, (Closed, Lock, true) => Locked, (Locked, Unlock, true) => Closed, _ => throw new InvalidOperationException($"Invalid transition") };
Em geral, você vê que padrões recursivos e expressões de chave podem levar a uma lógica de programa mais clara e declarativa.
Outros recursos do C # 8.0 na Visualização 2
Apesar do fato de que no VS 2019 Preview 2 as principais funções para trabalhar com padrões são as mais importantes, existem várias menores que, espero, você também achará útil e interessante. Não entrarei em detalhes, apenas faça uma breve descrição de cada um.
Usando anúncios
No uso de C # , os operadores sempre aumentam o nível de aninhamento, o que pode ser muito irritante e com baixa legibilidade. Em casos simples, quando você só precisa limpar o recurso no final do escopo, são usadas declarações. As declarações using são simplesmente declarações de variáveis locais com a palavra-chave using na frente delas, e seu conteúdo é colocado no final do bloco de instruções atual. Portanto, em vez de:
static void Main(string[] args) { using (var options = Parse(args)) { if (options["verbose"]) { WriteLine("Logging..."); } ... }
Você pode escrever
static void Main(string[] args) { using var options = Parse(args); if (options["verbose"]) { WriteLine("Logging..."); } }
Estruturas descartáveis ref
As estruturas de referência foram introduzidas no C # 7.2 e parece não haver lugar para repetir sobre elas aqui. Mas, ainda assim, vale a pena notar algo: eles têm algumas limitações, como a impossibilidade de implementar interfaces. Agora, as estruturas de referência podem ser usadas sem implementar a interface IDisposable , simplesmente usando o método Dispose nelas.
Funções locais estáticas
Se você deseja garantir que sua função local não incorra em custos de tempo de execução associados à “captura” (referência) de variáveis do escopo, você pode declarar como estática. Em seguida, o compilador impedirá o link para tudo o que é declarado nas funções anexas - exceto para outras funções locais estáticas!
Alterações da visualização 1
As principais funções da Visualização 1 eram tipos de referência anuláveis e fluxos assíncronos. Ambos os recursos mudaram um pouco na Visualização 2; portanto, se você começou a usá-los, é útil saber o seguinte.
Tipos de referência anuláveis
Adicionamos mais opções para gerenciar avisos anuláveis tanto na fonte (via diretivas de aviso #nullable quanto #pragma ) e no nível do projeto. Também alteramos a assinatura do arquivo do projeto para <NullableContextOptions> enable </ NullableContextOptions> .
Threads assíncronos
Alteramos o formato da interface IAsyncEnumerable <T> que o compilador espera. Isso leva ao fato de que o compilador não sincroniza com a interface fornecida no .NET Core 3.0 Preview 1, o que pode causar alguns problemas. No entanto, o .NET Core 3.0 Preview 2 será lançado em breve e isso retornará a sincronização.