C#: Bug ou Feature?
Tags: csharp, dotnet, compiler, programming
Published: 2023-12-15 20:56:42


![Barata de terno e gravada saindo de dentro de um monitor de computador](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c6vy1q2rer8p2w0uq45x.jpg)

Eu sempre estou estudando. 

Dedico muito tempo para ir cada vez mais a fundo em temas que envolvem programação e para conhecer as entranhas das tecnologias que eu utilizo no meu dia a dia.

Geralmente eu crio cenários ou situações imaginárias e as tento resolver utilizando aquilo que eu aprendi durante essa longa jornada de mais de 20 anos colocando ~~bugs~~ sistemas em produção. Isso me força sempre a revisitar conceitos e olhar como outras tecnologias poderiam resolver essas mesmas situações.

**Esse é o meio que eu utilizo para me manter relevante dentro da minha profissão: Aprendizado contínuo!**

Para fixar aquilo que eu (supostamente) aprendi, eu tento, dentro da minha cabeça com meus amigos imaginários, explicar como eu resolvi determinado problema e como a tecnologia que eu usei me ajudou a fazer isso. Na maioria das vezes eu crio anotações que agora estou organizando de tal forma que no futuro eu as use em posts, vídeos e ou palestras.

Nessa jornada eu percebi que as vezes eu acabo assumindo que muitas coisas são obvias e, quando eu vou transmitir esse conhecimento, acabo não me dando conta disso. 
Eu tenho exercitado meu cérebro cada vez mais para tentar identificar esse tipo de situação, justamente para que eu consiga reformular a minha ideia e repassar aquilo que eu sei da melhor maneira possível. 
**O que parece ser óbvio para mim, pode não parecer para você. Fato!**

---

**Antes de continuar, #VemCodar com a gente!!**

Tá afim de criar APIs robustas com .NET?

Feito **de dev para dev**, direto das trincheiras, por quem coloca **software em produção há mais de 20 anos**, o curso ["APIs robustas com ASP.NET Core 8, Entity Framework, Docker e Redis"](https://www.sympla.com.br/evento-online/apis-robustas-com-asp-net-core-8-entity-framework-docker-e-redis/2315443?referrer=www.vemcodar.com.br) traz de maneira direta, sem enrolação, tudo que você precisa saber para construir suas APIs de tal forma que elas sejam seguras, performáticas, escaláveis e, acima de tudo, dentro de um ambiente de desenvolvimento totalmente configurado utilizando Docker e Docker Compose. 

Não fique de fora! Dê um Up na sua carreira!

**O treinamento acontecerá nos dias 27/02, 28/02 e 29/02, online, das 19.30hs às 22.30hs**

**Acesse: [https://vemcodar.com.br/](https://vemcodar.com.br/)**

---

Esses dias me peguei analisando um trecho de código `csharp` onde eu estava testando as novidades da última versão e num determinado momento me deparei num cenário onde algo funcionava de um jeito que era óbvio pra mim, mas que nitidamente uma pessoa que está começando na programação ou que não conhece muito bem como as coisas funcionam por debaixo do capô ia ficar com cara de [Jackie Chan](https://pt.wikipedia.org/wiki/Jackie_Chan):

![Imagem do Jackie Chan com o texto Wait What?](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w1jy72v9dadtshrtlv39.jpeg)

Vou remontar o cenário de uma forma mais simples e objetiva aqui, sendo assim, avalie o código abaixo:

```csharp
var maria = new Pessoa(1, "Maria", 28);  
var mariaPodeDirigir = maria.PodeDirigir();  
Console.WriteLine($"Maria pode dirigir? {mariaPodeDirigir}");  
return;  
  
public record Pessoa(int Id, string Nome, uint Idade);  
  
public static class ExtensionMethods  
{  
    public static bool PodeDirigir(this Pessoa? pessoa)  
        => pessoa?.Idade >= 18;  
}
```

Esse código é bem simples e acredito que seja bem descritivo. (Eu supondo obviedades novamente...)

Temos um [_record_](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record) chamado `Pessoa` que contém as propriedades `Id`, `Nome` e `Idade`. Em seguida temos uma classe estática chamada `ExtensionMethods` contendo um método de extensão chamado `PodeDirigir` que valida se a idade de uma determinada pessoa é maior ou igual a 18. 

Métodos de extensão permitem que você "adicione" métodos a tipos existentes sem criar um novo tipo derivado, recompilar ou modificar de outra forma o tipo original.
Nesse caso, toda `Pessoa` terá um método chamado `PodeDirigir`. Aquele `this` antes do parâmetro `pessoa` me indica isso. 

E caso queira saber mais sobre _Extension Methods_ clique [aqui](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods).

Ao executar o código acima, o retorno é:

```bash
Maria pode dirigir? True
```

Até aqui ok, certo?

Vamos avançar para um outro cenário. 
Avalie o código abaixo:

```csharp
var maria = new Pessoa(1, "Maria", 28);  
maria = null;  /* SETANDO COMO NULO PRA VER O CIRCO PEGAR FOGO */
Console.WriteLine($"Maria tem {maria.Idade} anos de idade");  
return;  
  
public record Pessoa(int Id, string Nome, uint Idade);  
  
public static class ExtensionMethods  
{  
    public static bool PodeDirigir(this Pessoa? pessoa)  
        => pessoa?.Idade >= 18;  
}
```

Ao executar esse trecho de código temos o seguinte retorno:

```bash
System.NullReferenceException: Object reference not set to an instance of an object.
   at Program.<Main>$(String[] args) in /Volumes/SSD/Git/BugOuFeature/BugOuFeature/Program.cs:line 3
```

**Object reference not set to an instance of an object!**

_NullReferenceException_ Claro!!

Após ser criada uma pessoa eu simplesmente seto a variável para `null` e em seguida eu tento acessar a propriedade `Idade`. 

**É óbvio que daria erro! Isso acontece desde a versão 0.0.1-pre-alpha do csharp!**

Ok, seguimos para um outro cenário muito parecido com esse!

```csharp
var maria = new Pessoa(1, "Maria", 28);  
maria = null; /* JA VI O CIRCO PEGAR FOGO, AGORA QUERO SABER O QUE VAI ACONTECER... */
var mariaPodeDirigir = maria.PodeDirigir();  
Console.WriteLine($"Maria pode dirigir? {mariaPodeDirigir}");  
Console.WriteLine($"Maria tem {maria.Idade} anos de idade"); 
return;  
  
public record Pessoa(int Id, string Nome, uint Idade);  
  
public static class ExtensionMethods  
{  
    public static bool PodeDirigir(this Pessoa? pessoa)  
        => pessoa?.Idade >= 18;  
}
```

Dado o exemplo anterior onde eu seto a variável `maria` para nulo e em seguida tento ler o valor da sua propriedade `Idade` e recebo um erro, qual seria o resultado do código acima?

**A) Maria pode dirigir? False**
**B) Object reference not set to an instance of an object!**
**C) Palmeiras não tem Mundial**
**D) Todas as alternativas anteriores**

Se você respondeu a letra **A**, acertou.
Se você respondeu a letra **B**, acertou também.
Se você respondeu a letra **C**, mais do que acertou, merece um prêmio!

Agora, se você respondeu a letra **D**, saberia explicar o que aconteceu?

Saca só que coisa interessante. Esse é o resultado da execução acima:

```bash
Maria pode dirigir? False
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object...
```

Primeiro imprimiu o texto `Maria pode dirigir? False` e em seguida lançou o erro `Object reference not set to an instance of an object`.

Um objeto nulo conseguiu invocar um método de extensão, mas não conseguiu invocar uma propriedade.

Para mim parece óbvio o motivo. Mas no fundo não é. 
Não faz sentido semanticamente, parece um _bug_ do compilador, mas na verdade é uma "_feature_".

**E o segredo encontra-se no método de extensão. Será que ele adiciona mesmo um método a um tipo existente? Qual mágica ele faz por debaixo do capô?**

Não se preocupe e como de costume pegue um café que eu explico tudinho...

---

## Métodos de Extensão não existem...


> Pra começar, quero trazer um post que eu escrevi sobre Açúcar Sintático: [Por debaixo do capô: csharp e Açúcar Sintático!](https://dev.to/angelobelchior/por-debaixo-do-capo-c-e-acucar-sintatico-3gji).
Nele eu explico o que é como funciona esse tal de _Syntactic Sugar_ ou para nós tupiniquins, _Açúcar Sintático_!

Eu gosto muito de usar o site [SharpLab](https://sharplab.io/). Nele eu consigo ver as entranhas do código _csharp_, consigo entender como o compilador resolve os famigerados _Açúcares Sintáticos_.

O compilador do _csharp_ - [Roslyn](https://github.com/dotnet/roslyn) - transforma o código escrito em [IL](https://en.wikipedia.org/wiki/Common_Intermediate_Language) e esse código é executado pelo _.Net Runtime_ via JIT ou compilado em AOT, aliás escrevi um post muito bom sobre esse tema, modéstia à parte: [.NET 8, JIT e AOT](https://dev.to/angelobelchior/net-8-jit-e-aot-25lk).

Usando o _SharpLab_ é possível ver o seu código _csharp_ compilado em [IL](https://en.wikipedia.org/wiki/Common_Intermediate_Language). 
Além disso é possível fazer engenharia reversa no [IL](https://en.wikipedia.org/wiki/Common_Intermediate_Language) transformando-o em _csharp_ (ou em vb.net).  E isso para nós é muito útil, afinal, dessa forma podemos ver todo o trabalho que o compilador faz para que a gente escreva o mínimo de código possível. Essa é a função dos _Açúcares Sintáticos_!

Sendo assim, vamos usar o _SharpLab_ para saber como o nosso código de exemplo é compilado.
Eu não vou explicar todo código gerado pelo _SharpLab_ trecho a trecho, já fiz isso no post citado logo acima, quero apenas focar no método de extensão, afinal, como disse anteriormente, é ele quem faz a mágica acontecer, ou melhor, é ele quem confunde tudo....

```csharp
public static class ExtensionMethods
{
    public static bool PodeDirigir(Pessoa pessoa)
    {
        if ((object)pessoa == null)
        {
            return false;
        }
        return pessoa.Idade >= 18;
    }
}
```

Já podemos notar logo de cara que o operador de nulo `?` se transforma num bom e velho `if`.
Outro ponto é que o indicador `this` da variável `pessoa` simplesmente some também. Ele serve justamente para indicar que o tipo `Pessoa` vai ter um método chamado `PodeDirigir`, certo? 

Mas não é isso que acontece de fato. 
Olha como o compilador do _csharp_ resolve a invocação desse método de extensão:

```csharp
Pessoa pessoa = new Pessoa(1, "Maria", 28u);
pessoa = null;
bool value = ExtensionMethods.PodeDirigir(pessoa); // <======
Console.WriteLine($"Maria pode dirigir? {value}");
Console.WriteLine($"Maria tem {pessoa.Idade} anos de idade");
// .....
```

**Os métodos de extensão não adicionam um novo método ao tipo!** 

O que temos é o método `PodeDirigir` sendo invocado normalmente recebendo o objeto `pessoa` como parâmetro.  Nesse cenário, tanto faz o objeto `pessoa` estar ou não nulo. 
Dentro desse método podemos notar que existe uma verificação e que se o parâmetro for `null`, o retorno do método é `false` e é por isso que recebemos a mensagem `Maria pode dirigir? False`.
Em seguida temos o trecho de código que vai dar erro, afinal estamos tentando acessar a propriedade `Idade` de um objeto nulo.

**Lendo o código gerado pelo compilador do _csharp_ fica claro o motivo pelo qual não recebemos erro de nulo ao chamar o método de extensão, afinal, ele não existe!**

Mas esse entendimento só fica claro a partir do momento em que aprendemos como as coisas funcionam por debaixo do capô. A gente leva um tempo até achar que isso é "óbvio", aliás, para quem está começando com _csharp_ esse tipo de situação é bem estranho, soa como um _bug_ e faz total sentido soar assim, afinal, a linguagem apresenta dois tipos de comportamentos diferentes para cenários iguais.

O _Açúcar Sintático_ simplifica a escrita de código e traz esse comportamento totalmente incompatível com a semântica da linguagem: Não deveria ser possível acessar um método a partir de um objeto nulo! Mas métodos de extensão permitem esse comportamento, afinal eles não adicionam um novo método ao tipo.

Quem não conhece a fundo vai achar que é um _bug_. Quem já se aventurou pelas entranhas do _csharp_ vai dizer que é uma _feature_.

Eu acho estranho, mas já me acostumei...

O que você acha disso? Coloca aí nos comentários.

Era isso. Muito obrigado! Até a próxima!