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!
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:
Vou remontar o cenário de uma forma mais simples e objetiva aqui, sendo assim, avalie o código abaixo:
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 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.
Ao executar o código acima, o retorno é:
Maria pode dirigir? True
Até aqui ok, certo?
Vamos avançar para um outro cenário.
Avalie o código abaixo:
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:
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!
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:
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!.
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. 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 - transforma o código escrito em IL 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.
Usando o SharpLab é possível ver o seu código csharp compilado em IL.
Além disso é possível fazer engenharia reversa no IL 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....
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:
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!
Top comments (0)