Como passei o verão com C # 8

Em uma versão recente do podcast DotNet & More Blazor, o NetCore 3.0 Preview, C # 8 e não apenas mencionamos casualmente um tópico tão ardente como o C # 8. A história sobre a experiência com o C # 8 não era grande o suficiente para lhe dedicar uma questão separada, por isso foi decidido compartilhar com ele os meios do gênero epistolar.


Neste artigo, gostaria de falar sobre minha experiência no uso do C # 8 na produção por 4 meses. Abaixo, você pode encontrar respostas para as seguintes perguntas:


  • Como "soletrar" no novo c #
  • Quais recursos foram realmente úteis
  • O que decepcionou

Uma lista completa dos recursos do C # 8 pode ser encontrada na documentação oficial da Microsoft . Neste artigo, omitirei as oportunidades que não pude experimentar por um motivo ou outro, a saber:


  • Membros somente leitura
  • Membros da interface padrão
  • Estruturas descartáveis ​​ref
  • Fluxos assíncronos
  • Índices e intervalos

Proponho começar com uma das mais deliciosas possibilidades, como me pareceu antes.


Alternar expressões


Nos nossos sonhos, apresentamos essa função de maneira bastante otimista:


int Exec(Operation operation, int x, int y) => operation switch { Operation.Summ => x + y, Operation.Diff => x - y, Operation.Mult => x * y, Operation.Div => x / y, _ => throw new NotSupportedException() }; 

Mas, infelizmente, a realidade faz seus próprios ajustes.
Em primeiro lugar, não há possibilidade de combinar as condições:


  string TrafficLights(Signal signal) { switch (signal) { case Signal.Red: case Signal.Yellow: return "stop"; case Signal.Green: return "go"; default: throw new NotSupportedException(); } } 

Na prática, isso significa que, na metade dos casos, a expressão do comutador precisará ser transformada em um comutador regular para evitar copiar e colar.


Em segundo lugar, a nova sintaxe não suporta instruções, ou seja, código que não retorna um valor. Parece, bem, e não é necessário, mas fiquei surpreso quando percebi com que frequência o comutador é usado (em conjunto com a correspondência de padrões) para algo como afirmação em testes.


Em terceiro lugar, a expressão switch, que segue do último parágrafo, não suporta manipuladores de várias linhas. Quão assustador entendemos no momento de adicionar os logs:


  int ExecFull(Operation operation, int x, int y) { switch (operation) { case Operation.Summ: logger.LogTrace("{x} + {y}", x, y); return x + y; case Operation.Diff: logger.LogTrace("{x} - {y}", x, y); return x - y; case Operation.Mult: logger.LogTrace("{x} * {y}", x, y); return x * y; case Operation.Div: logger.LogTrace("{x} / {y}", x, y); return x / y; default: throw new NotSupportedException(); } } 

Eu não quero dizer que a nova opção é ruim. Não, ele é bom, apenas não é bom o suficiente.


Padrões de propriedade e posicionais


Há um ano, eles me pareciam os principais candidatos ao título de "oportunidade que mudou o desenvolvimento". E, como esperado, para usar todo o poder dos padrões posicionais e de propriedade, você precisa mudar sua abordagem ao desenvolvimento. Ou seja, é necessário imitar tipos de dados algébricos.
Parece, qual é o problema: pegue a interface do marcador e vá embora. Infelizmente, esse método tem uma séria desvantagem em um projeto grande: ninguém garante acompanhar em tempo de design a expansão de seus tipos algébricos. Portanto, é muito provável que, com o tempo, as alterações no código levem a muitas "falhas no padrão" nos locais mais inesperados.


Padrões de tupla


Mas o "irmão mais novo" das novas possibilidades de comparação com a amostra mostrou-se realmente bem-feito. O fato é que o padrão de tupla não requer nenhuma alteração na arquitetura familiar do nosso código, apenas simplifica alguns casos:


  Player? Play(Gesture left, Gesture right) { switch (left, right) { case (Gesture.Rock, Gesture.Rock): case (Gesture.Paper, Gesture.Paper): case (Gesture.Scissors, Gesture.Scissors): return null; case (Gesture.Rock, Gesture.Scissors): case (Gesture.Scissors, Gesture.Paper): case (Gesture.Paper, Gesture.Rock): return Player.Left; case (Gesture.Paper, Gesture.Scissors): case (Gesture.Rock, Gesture.Paper): case (Gesture.Scissors, Gesture.Rock): return Player.Right; default: throw new NotSupportedException(); } } 

Mas a melhor parte é que esse recurso, que é previsível o suficiente, funciona muito bem com o método Deconstruct. Basta passar uma classe com o Deconstruct implementado para alternar e usar os recursos do padrão de tupla.


Usando declarações


Parece uma oportunidade menor, mas traz muita alegria. Em todas as promoções, a Microsoft fala sobre esse aspecto como reduzir o aninhamento. Mas sejamos honestos, não tanto o que importa. Mas o que é realmente sério são os efeitos colaterais da exclusão de um bloco de código:


  • Freqüentemente, ao adicionar usando, precisamos puxar o código "dentro" do bloco usando o método copiar e colar. Agora não pensamos nisso
  • As variáveis ​​declaradas dentro de using e usadas após Dispose do objeto using são uma verdadeira dor de cabeça. Um problema a menos
  • Nas aulas que exigem chamadas Dispose frequentes, cada método teria duas linhas a mais. Parece um pouco, mas na condição de muitos métodos pequenos, esse pouco não permite exibir um número suficiente desses mesmos métodos em uma tela

Como resultado, o simples fato de usar declarações altera tanto a sensação de codificação que você simplesmente não deseja retornar ao c # 7.3.


Funções locais estáticas


Para ser sincero, se não fosse pela ajuda da análise de código, eu nem notaria essa possibilidade. No entanto, ela firmemente se estabeleceu em meu código: afinal, funções locais estáticas são perfeitamente adequadas para o papel de pequenas funções puras, pois não podem suportar o fechamento de variáveis ​​de método. Como resultado, é mais fácil para o coração, porque você entende que há um erro potencial a menos no seu código.


Tipos de referência anuláveis


E para a sobremesa, gostaria de mencionar a característica mais importante do C # 8. Na verdade, a análise de tipos de referência anuláveis ​​merece um artigo separado. Eu só quero descrever as sensações.


  • Em primeiro lugar, é maravilhoso. Eu poderia ter descrito anteriormente minha intenção explícita de declarar um campo ou propriedade anulável, mas agora essa função está embutida no idioma.
  • Em segundo lugar, ele não salva nada de NullReferenceException. E não estou falando sobre o notório "entupimento" de avisos. É que, no tempo de execução, ninguém gera nenhuma verificação de argumento nulo para você; portanto, não se apresse em lançar código como throw new ArgumentNullException ()
  • Em terceiro lugar, há um problema sério com o DTO. Por exemplo, você anota uma propriedade com o atributo Requerido. Assim, um objeto com uma propriedade 100% não nula entrará no seu controlador WebAPI. No entanto, não é possível associar esse atributo e todos os atributos semelhantes às verificações de tipos de referência que permitem valor nulo. O fato é que, se você declarar MyProperty padrão {get; set;} uma propriedade com um tipo NotNull, você receberá um aviso: "[CS8618] A propriedade não anulável 'MyProperty' não foi inicializada. Considere declarar a propriedade como anulável" . O que é justo o suficiente, pois você não pode garantir uma semântica não nula durante o processo de inicialização. O único resultado desse recurso é a incapacidade de usar propriedades não nulas em qualquer DTO. Mas há boas notícias, há uma solução simples - basta inicializar seu campo com o valor padrão:
     public string MyProperty { get; set; } = ""; 
  • Quarto, os atributos que lidam com casos complexos, como TryGetValue, são eles próprios bastante complexos. Como resultado, é altamente provável que desenvolvedores pouco conscientes abusem dos operadores (!), Nivelando os recursos dos tipos de referência anuláveis. Uma esperança para os analisadores.
  • Quinto, e mais importante, pessoalmente, essa oportunidade já me salvou muitas vezes dos erros do NullReferenceException. Acontece uma economia de tempo banal - muitos erros são detectados no estágio de compilação, e não testes ou depuração. Isso é especialmente verdade não apenas no processo de desenvolvimento de lógica de negócios complexa, mas também no caso de trabalho trivial com bibliotecas externas, DTO e outras dependências, possivelmente contendo nulo.

Sumário


Obviamente, as oportunidades apresentadas não alcançam uma revolução completa, mas há cada vez menos hiato entre C # e F # / Scala. Se é bom ou ruim, o tempo dirá.


No momento do lançamento deste artigo, o C # 8 já havia se estabelecido em seu projeto, então eu estaria me perguntando, quais são seus sentimentos sobre a nova versão do nosso idioma favorito?

Source: https://habr.com/ru/post/pt467655/


All Articles