Finalmente, o C # 7 tem um recurso aguardado chamado Correspondência de Padrões. Se você estiver familiarizado com linguagens funcionais, como F #, essa função na forma em que ela existe atualmente pode decepcioná-lo um pouco. Mas ainda hoje, ele pode simplificar o código em vários casos. Mais detalhes sob o corte!

Cada novo recurso pode ser perigoso para o desenvolvedor, criando o aplicativo para o qual o desempenho é crítico. Novos níveis de abstração são bons, mas para usá-los de maneira eficaz, é necessário entender como eles realmente funcionam. Este artigo discute a função de correspondência de padrões e como ela funciona.
Uma amostra em C # pode ser usada em uma expressão é, bem como no bloco de caso de uma instrução switch.
Existem três tipos de amostras:
- constante de amostra;
- tipo de amostra;
- variável de amostra.
A correspondência de padrões em é expressões
public void IsExpressions(object o) {
Usando a expressão is, é possível verificar se o valor é constante e, usando a verificação de tipo, é possível determinar adicionalmente a variável de amostra.
Ao usar a correspondência de padrões nas expressões is, você deve prestar atenção a vários pontos interessantes:
- A variável inserida pela instrução if é enviada para o escopo externo.
- A variável inserida pela instrução if é explicitamente designada apenas quando o padrão corresponde.
- A implementação atual da correspondência de padrões nas expressões não é muito eficiente.
Primeiro, considere os dois primeiros casos:
public void ScopeAndDefiniteAssigning(object o) { if (o is string s && s.Length != 0) { Console.WriteLine("o is not empty string"); }
A primeira instrução if apresenta a variável s, visível dentro de todo o método. Isso é razoável, mas complica a lógica se outras instruções if no mesmo bloco tentarem reutilizar o mesmo nome. Nesse caso, certifique-se de usar um nome diferente para evitar conflitos.
A variável inserida na expressão is é atribuída explicitamente apenas quando o predicado é verdadeiro. Isso significa que a variável n na segunda instrução if não está atribuída no operando certo, mas como já está declarada, podemos usá-la como a variável out no método int.TryParse.
O terceiro ponto mencionado acima é o mais importante. Considere o seguinte exemplo:
public void BoxTwice(int n) { if (n is 42) Console.WriteLine("n is 42"); }
Na maioria dos casos, a expressão é convertida em object.Equals (constante, variável) [embora as características digam que o operador == deve ser usado para tipos simples]:
public void BoxTwice(int n) { if (object.Equals(42, n)) { Console.WriteLine("n is 42"); } }
Esse código chama dois processos de conversão de empacotamento que podem afetar significativamente o desempenho se usados em um caminho crítico do aplicativo. Anteriormente, a expressão o é nula chamada de empacotamento, se a variável o era de um tipo anulável (consulte
Código subótimo para e é nulo ), mas há esperança de que isso seja corrigido (aqui está a
solicitação correspondente
no github ).
Se a variável n for do tipo objeto, a expressão o é 42 causará um processo de "conversão de embalagem" (para o literal 42), embora um código semelhante baseado na instrução switch não leve a isso.
Variável de amostra em is expression
Um padrão variável é um tipo especial de tipo de padrão com uma grande diferença: o padrão corresponderá a qualquer valor, mesmo nulo.
public void IsVar(object o) { if (o is var x) Console.WriteLine($"x: {x}"); }
A expressão o is object será verdadeira se o não for nulo, mas a expressão o for var x será sempre verdadeira. Portanto, o compilador no modo de liberação * exclui completamente as instruções if e simplesmente sai da chamada do método Console. Infelizmente, o compilador não avisa sobre a indisponibilidade do código no seguinte caso: if (! (O é var x)) Console.WriteLine ("Inacessível"). Há esperança de que isso também seja corrigido.
* Não está claro por que o comportamento difere apenas no modo de liberação. Parece que a raiz de todos os problemas é a mesma: a implementação inicial da função não é ideal. No entanto, a julgar por esse comentário de Neal Gafter, tudo mudará em breve: “O código para correspondência com a amostra será reescrito do zero (para também suportar amostras recursivas). Eu acho que a maioria das melhorias que você está falando será implementada no novo código e disponível gratuitamente. No entanto, isso levará algum tempo. ”A ausência de uma verificação nula torna essa situação especial e potencialmente perigosa. No entanto, se você souber exatamente como esse exemplo funciona, poderá ser útil. Pode ser usado para introduzir uma variável temporária na expressão:
public void VarPattern(IEnumerable<string> s) { if (s.FirstOrDefault(o => o != null) is var v && int.TryParse(v, out var n)) { Console.WriteLine(n); } }
É expressão e declaração de Elvis
Há outro caso que pode ser útil. Um tipo de amostra corresponde a um valor somente quando não é nulo. Podemos usar essa lógica de "filtragem" com um operador de distribuição nula para tornar o código mais legível:
public void WithNullPropagation(IEnumerable<string> s) { if (s?.FirstOrDefault(str => str.Length > 10)?.Length is int length) { Console.WriteLine(length); }
Observe que o mesmo padrão pode ser usado para os tipos de valor e de referência.
Correspondência de padrões em blocos de caso
A funcionalidade da instrução switch foi estendida no C # 7, para que agora os padrões possam ser usados nas cláusulas case:
public static int Count<T>(this IEnumerable<T> e) { switch (e) { case ICollection<T> c: return c.Count; case IReadOnlyCollection<T> c: return c.Count;
Este exemplo mostra o primeiro conjunto de alterações na instrução switch.
- Uma variável de qualquer tipo pode ser usada com a instrução switch.
- A cláusula case permite especificar um padrão.
- A ordem das cláusulas do caso é importante. O compilador lançará um erro se a sentença anterior corresponder ao tipo base e a próxima à derivada.
- As ofertas personalizadas são implicitamente verificadas como nulas **. No exemplo acima, a última cláusula case é válida porque corresponde apenas quando o argumento não é nulo.
** A última frase do caso mostra outra função adicionada no C # 7 - amostras de uma variável vazia. O nome especial _ informa ao compilador que a variável não é necessária. O exemplo de tipo na cláusula case requer um alias. Mas se você não precisar, pode usar _.O fragmento a seguir mostra outro recurso da correspondência de padrões com base na instrução switch - a capacidade de usar predicados:
public static void FizzBuzz(object o) { switch (o) { case string s when s.Contains("Fizz") || s.Contains("Buzz"): Console.WriteLine(s); break; case int n when n % 5 == 0 && n % 3 == 0: Console.WriteLine("FizzBuzz"); break; case int n when n % 5 == 0: Console.WriteLine("Fizz"); break; case int n when n % 3 == 0: Console.WriteLine("Buzz"); break; case int n: Console.WriteLine(n); break; } }
Esta é uma versão estranha da tarefa do
FizzBuzz que processa um objeto, não apenas um número.
Uma instrução switch pode incluir várias cláusulas de caso do mesmo tipo. Nesse caso, o compilador combina todas as verificações de tipo para evitar cálculos desnecessários:
public static void FizzBuzz(object o) {
Mas há duas coisas a ter em mente:
1. O compilador combina apenas verificações seqüenciais de tipo e, se você misturar cláusulas de maiúsculas e minúsculas com tipos diferentes, um código de qualidade inferior será gerado:
switch (o) {
O compilador irá convertê-lo da seguinte maneira:
if (o é int n && n == 1) retorna 1;
if (o is string s && s == "") return 2; if (o is int n2 && n2 == 2) return 3; return -1;
2. O compilador faz todo o possível para evitar problemas típicos de seqüenciamento.
switch (o) { case int n: return 1;
No entanto, o compilador não pode determinar se um predicado é mais forte que outro e substitui efetivamente as seguintes cláusulas de caso:
switch (o) { case int n when n > 0: return 1;
Resumo de correspondência de padrões
- Os seguintes padrões apareceram no C # 7: um padrão constante, um padrão de tipo, um padrão variável e um padrão variável vazio.
- As amostras podem ser usadas em expressões e em blocos de caso.
- A implementação do padrão constante na expressão é para tipos de valor longe do ideal em termos de desempenho.
- As amostras de uma variável sempre correspondem; é preciso ter cuidado com elas.
- A instrução switch pode ser usada para definir verificações de tipo com predicados adicionais nas cláusulas when.
Evento Unity em Moscou - Unity Moscow Meetup 2018.1
Na quinta-feira, 11 de outubro, o Unity Moscow Meetup 2018.1 será realizado na Escola Superior de Economia. Esta é a primeira reunião de desenvolvedores do Unity em Moscou nesta temporada. O tema do primeiro mitap será AR / VR. Você encontrará relatórios interessantes, comunicação com profissionais do setor e uma zona de demonstração especial da MSI.
Detalhes