Por debaixo do capô: csharp e Açúcar Sintático!
Tags: csharp, compiler, syntacticsugar, magic
Published: 2023-10-10 04:06:00

Você já ouviu falar em "açúcar sintático" em programação? Não, eu não estou falando de doces ou guloseimas, mas sim de uma técnica que torna a escrita de código mais doce para quem desenvolve, ~~afinal de amarga já basta a vida~~. Hoje quero mostrar como o **csharp** explora essa técnica de forma maravilhosamente linda.

---

**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/)**

---

## Mas o que é Açúcar Sintático?

O açúcar sintático é uma característica das linguagens de programação que oferece uma maneira mais concisa ou mais "doce" de escrever código. Ele não altera a funcionalidade do código, mas simplifica a sintaxe, tornando-a mais legível e menos verbosa. 

> O termo **Açúcar Sintático** foi criado por [Peter J. Landin](https://en.wikipedia.org/wiki/Peter_Landin) em 1964 para descrever a sintaxe de um [ALGOL](https://pt.wikipedia.org/wiki/ALGOL) simples - como a linguagem de programação que foi definida semanticamente nos termos da aplicação das expressões do Cálculo lambda, centrada na substituição léxica do **λ** com **"where"**. ([fonte wikipedia](https://pt.wikipedia.org/wiki/A%C3%A7%C3%BAcar_sint%C3%A1tico))

## Açúcar Sintático em **csharp**

O **csharp** é cheio de truques por debaixo do capô. A gente utiliza no dia-a-dia e as vezes nem sequer para pra pensar como as coisas funcionam.

Quero começar com um exemplo simples. **Propriedades**. 

As propriedades começaram com essa característica de escrita logo nas primeiras versões do dotnet:

```csharp
private string _nome;
public string Nome
{
    get { return _nome; }
    set { _nome = value; }
}
```

Depois evoluíram para isso:

```csharp
public string Nome { get; set; }
```

Muito mais simples, certo?

Mas afinal, como que isso funciona por debaixo do capô? Como que o compilador do **csharp** resolve esse tipo de código?

Ao ser compilado, os dois exemplos acima resultam no mesmo código:

```csharp
    private string k__BackingField;

    public void set_Nome(string value)
    {
        k__BackingField = value;
    }
    
    public string get_Nome()
    {
        return k__BackingField;
    }
```

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

Sim, as propriedades nada mais são do que métodos, assim como é feito em Java, só que de um jeito muito mais inteligente. No caso, a variável `k__BackingField` pode ter outro nome, o compilador se encarrega em definir qual.

Se você duvida, segue um print do Rider:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eaprxsx6w86frsi62ieb.png)

Na imagem é mostrado que existem erros no `get` e no `set` da propriedade `Nome`, e a mensagem que o compilador gera para cada um desses erros é "_**Member with the same signature is already declared**_".

Interessante, não é? Isso é só o começo.

---

Vamos para o `var`. Esse é um caso clássico. 

Basicamente o compilador infere o tipo do dado e substitui o `var` pelo tipo correto:

```csharp
// Escrito por mim
var nome = "Angelo";
```

```csharp
// Gerado pelo compilador
string nome = "Angelo";
```

E **tipos numéricos**? Como o compilador sabe se é um `int`, `float`, `decimal` ou `double`?
Bem, nesse caso você vai precisar deixar explícito, caso contrário, **o compilador assume que é um inteiro**.

```csharp
// Escrito por mim
var meuInt = 0;
var meuDouble = 0d;
var meuFloat = 0f;
var meuDecimal = 0m;
```

```csharp
// Gerado pelo compilador
int num = 0;
double num2 = 0.0;
float num3 = 0f;
decimal num4 = default(decimal);
```

Bacana, não? Bom, eu acho! Ainda mais que é possível notar que os nomes das variáveis não necessariamente serão mantidos pelo compilador. Curioso.

---

E o que acontece quando **uma variável é criada, mas não é usada**?

```csharp
// Escrito por mim
int a = 62; // <---- não usada
       
int b = 45;
Console.WriteLine(b);
```

```csharp
// Gerado pelo compilador
Console.WriteLine(45);
```

Nada. Simples assim. O compilador simplesmente a ignora.

---

Vamos para o próximo: **Tipo Anônimo**!
Analise o seguinte código:

```csharp
// Escrito por mim
var pontos = new { x = 1, y = 2};
Console.WriteLine(pontos.x);        
Console.WriteLine(pontos.y);
```
A variável pontos é um objeto. Isto é um fato. Todo tipo herda de _object_ em **csharp**. Porém o tipo _object_ não tem as propriedades _x_ nem _y_. Então qual é a mágica que o compilador faz para que isso funcione?

Respira fundo...

```csharp
// Gerado pelo compilador
[CompilerGenerated]
[DebuggerDisplay("\\{ x = {x}, y = {y} }", Type = "<Anonymous Type>")]
internal sealed class <>f__AnonymousType0<<x>j__TPar, <y>j__TPar>
{
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly <x>j__TPar <x>i__Field;

    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly <y>j__TPar <y>i__Field;

    public <x>j__TPar x
    {
        get
        {
            return <x>i__Field;
        }
    }

    public <y>j__TPar y
    {
        get
        {
            return <y>i__Field;
        }
    }

    [DebuggerHidden]
    public <>f__AnonymousType0(<x>j__TPar x, <y>j__TPar y)
    {
        <x>i__Field = x;
        <y>i__Field = y;
    }

    [DebuggerHidden]
    public override bool Equals(object value)
    {
        <>f__AnonymousType0<<x>j__TPar, <y>j__TPar> anon = value as <>f__AnonymousType0<<x>j__TPar, <y>j__TPar>;
        return this == anon || (anon != null && EqualityComparer<<x>j__TPar>.Default.Equals(<x>i__Field, anon.<x>i__Field) && EqualityComparer<<y>j__TPar>.Default.Equals(<y>i__Field, anon.<y>i__Field));
    }

    [DebuggerHidden]
    public override int GetHashCode()
    {
        return (959378729 * -1521134295 + EqualityComparer<<x>j__TPar>.Default.GetHashCode(<x>i__Field)) * -1521134295 + EqualityComparer<<y>j__TPar>.Default.GetHashCode(<y>i__Field);
    }

    [DebuggerHidden]
    public override string ToString()
    {
        object[] array = new object[2];
        <x>j__TPar val = <x>i__Field;
        array[0] = ((val != null) ? val.ToString() : null);
        <y>j__TPar val2 = <y>i__Field;
        array[1] = ((val2 != null) ? val2.ToString() : null);
        return string.Format(null, "{{ x = {0}, y = {1} }}", array);
    }
}

<>f__AnonymousType0<int, int> anon = new <>f__AnonymousType0<int, int>(1, 2);
Console.WriteLine(anon.x);
Console.WriteLine(anon.y);
```

Que loucura!!! Olha o tanto de código gerado!!! O compilador do **csharp** gera uma classe com as propriedades `x` e `y`. O nome dessa classe é `<>f__AnonymousType0` e tem dois tipos genéricos `<x>j__TPar` e `<y>j__TPar`.

Claramente, se você copiar e colar esse código na sua IDE ele não vai compilar, afinal, não é possível criar nomes de classes em csharp que contenham os caracteres _<>_. 

Mas de onde surgiu esse nome? Do [IL - Intermediate Language](https://en.wikipedia.org/wiki/Common_Intermediate_Language). Em _IL_ é possível ter esses caracteres nos nomes de classes, variáveis, tipos e etc.
Abaixo segue um trecho de código IL da classe anônima gerada:


```csharp
.class private auto ansi sealed beforefieldinit '<>f__AnonymousType0`2'<'<x>j__TPar', '<y>j__TPar'>
    extends [System.Runtime]System.Object
...
```

O compilador é muito trabalhador! Ele criou o construtor e os métodos `Equals`, `GetHashCode` e `ToString`. Com isso, temos uma classe muito bem definida e estruturada. De anônima, só o nome mesmo!

Além de trabalhar bastante, ele trabalha com inteligência. Se você criar um outro objeto anônimo com as mesmas propriedades, a classe criada é reaproveitada, sendo assim, as duas variáveis criadas abaixo vão utilizar a mesma classe `<>f__AnonymousType0`:

```csharp
var pontos = new { x = 10, y = 20};
var pontos2 = new { x = 30, y = 4o};
``` 

---

Vamos avançando!
Sabe o **`foreach`**? E se eu te disser que ele não existe?

Abaixo temos um código simples, onde é criada uma lista de `string` e depois eu _varro_ ela, imprimindo cada item.

```csharp
// Escrito por mim
var items = new List<string>();
items.Add("A");        
items.Add("B");
items.Add("C");

foreach(var item in items)
{
    Console.WriteLine(item);
}
```

Porém, analisando o código abaixo, nota-se que não existe uma instrução `foreach`! Basicamente o que se tem é o uso do [_Iterator Pattern_](https://en.wikipedia.org/wiki/Iterator_pattern)! 

```csharp
// Gerado pelo compilador
List<string> list = new List<string>();
list.Add("A");
list.Add("B");
list.Add("C");
List<string>.Enumerator enumerator = list.GetEnumerator();
try
{
    while (enumerator.MoveNext())
    {
        string current = enumerator.Current;
        Console.WriteLine(current);
    }
}
finally
{
    ((IDisposable)enumerator).Dispose();
}
```

---

Falando em `IDisposable`, como será que o compilador lida com a instrução `using`?

```csharp
// Escrito por mim
using (var disposable = new Disposable())
{
    disposable. Execute();
}
```

```csharp
// Gerado pelo compilador
Disposable disposable = new Disposable();
try
{
    disposable.Execute();
}
finally
{
    if (disposable != null)
    {
        ((IDisposable)disposable).Dispose();
    }
}
```
Ele utiliza um singelo `try/finally`, invocando o método `Dispose` da interface no `finally`. 

---

Bora falar sobre **Interpolação de Strings**!

**Interpolação de Strings** é uma das coisas mais legais que temos no **csharp**. Essa _feature_ facilita muito a escrita de textos que contenham variáveis dentro:

```csharp
// Escrito por mim
var nome = "Angelo";
var frase = $"Meu nome é {nome}";
Console.WriteLine(frase);  
```

No código abaixo podemos notar o quão inteligente é o compilador do **csharp**. Nesse exemplo simples, o compilador utiliza o método `Concat` da `string`.

```csharp
// Gerado pelo compilador
string text = "Angelo";
string value = string.Concat("Meu nome é ", text);
Console.WriteLine(value);
```

Porém, caso tenhamos mais de uma variável na concatenação, a escrita de código muda completamente, focando na **performance** e em **não disperdiçar memória**.

Vejamos esse exemplo.

```csharp
// Escrito por mim
var nome = "Angelo";
var idade = 39;
var cidade = "São Paulo";
var time = "Corinthians";
var frase = $"Meu nome é {nome}, tenho {idade} anos, moro na cidade de {cidade} e torço para o time {time}";
Console.WriteLine(frase); 
```
E o resultado:

```csharp
// Gerado pelo compilador
string value = "Angelo";
int value2 = 39;
string value3 = "São Paulo";
string value4 = "Corinthians";
DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(65, 4);
defaultInterpolatedStringHandler.AppendLiteral("Meu nome é ");
defaultInterpolatedStringHandler.AppendFormatted(value);
defaultInterpolatedStringHandler.AppendLiteral(", tenho ");
defaultInterpolatedStringHandler.AppendFormatted(value2);
defaultInterpolatedStringHandler.AppendLiteral(" anos, moro na cidade de ");
defaultInterpolatedStringHandler.AppendFormatted(value3);
defaultInterpolatedStringHandler.AppendLiteral(" e torço para o time ");
defaultInterpolatedStringHandler.AppendFormatted(value4);
string value5 = defaultInterpolatedStringHandler.ToStringAndClear();
Console.WriteLine(value5);
```
Note que coisa interessantíssima temos aqui. O compilador utilizou a _struct_ [_DefaultInterpolatedStringHandler_](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.defaultinterpolatedstringhandler?view=net-7.0) que fica no namespace _System.Runtime.CompilerServices_ para fazer a concatenação da string. Esse processo é muito mais eficiente do que um _string.Concat_ ou _string.Format_. 

Nesse [post](https://sergeyteplyakov.github.io/Blog/c%2310/2021/11/08/Dissecing-Interpolated-Strings-Improvements-In-CSharp-10.html) o [Sergey Teplyakov](https://github.com/SergeyTeplyakov), que é um engenheiro da Microsoft, dá detalhes de como o _DefaultInterpolatedStringHandler_ funciona por debaixo do capô e o quão performático ele é em relação ao outros métodos. Vale muito a pena ler!

---

Vamos em frente, e agora eu quero falar sobre [**Extension Methods**](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods). 

Essa funcionalidade, como o próprio nome diz permite adicionar novos métodos em tipos já existentes. 

Um bom exemplo são os métodos do [_LINQ_](https://learn.microsoft.com/en-us/dotnet/csharp/linq/) para as listas. Basta incluir o namespace _System.Linq_ que nossas listas terão suportes para métodos como `Where`, `FirstOrDefault`, `GroupBy`, `Select`, etc.

Criar Extension Methods é bem simples, segue um exemplo.

```csharp
// Escrito por mim
public static class MarkDownExtensionMethods
{
    public static string ToItalic(this string value)
    {
        return $"_{value}_";
    }
}

var md = "Esse é um texto em itálico".ToItalic();
Console.WriteLine(md);
```
Basicamente, no método `ToItalic` temos um parâmetro _string_ chamado `value`. Porém, o que transforma esse método em uma extensão são o fatos da classe e o método `ToItalic` serem estáticos, além do uso do `this` no parâmetro `value`. Isso quer dizer que toda _string_ terá o método `ToItalic`.

E como é que o compilador resolve isso? Será que temos aqui uma mágica mais rebuscada? Não! Basicamente é utilizado o método `MarkDownExtensionMethods.ToItalic` passando uma string como parâmetro. Simples, simples, simples. Eu confesso que não acreditei que era algo tão simplório quanto isto:

```csharp
// Gerado pelo compilador
[Extension]
public static class MarkDownExtensionMethods
{
    [System.Runtime.CompilerServices.NullableContext(1)]
    [Extension]
    public static string ToItalic(string value)
    {
        return string.Concat("_", value, "_");
    }
}

string value = MarkDownExtensionMethods.ToItalic("Esse é um texto em itálico");
Console.WriteLine(value);
```

---

E pra fechar, eu escolhi demonstrar os [**_Records_**](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record). _Record_ é algo sensacional, muito prático e resolve muitos problemas. _Record_ pode ser uma classe, ou uma struct. A grande sacada aqui é que é um objeto **[imutável](https://pt.wikipedia.org/wiki/Objeto_imut%C3%A1vel)**, isto é, após sua criação não é possível alterar suas propriedades.

```csharp
// Escrito por mim

public record Pessoa(string Nome, int Idade);

//Cria um objeto imutável
var angelo = new Pessoa("Angelo", 39);
Console.WriteLine(angelo);

// Cria um novo objeto imutável a partir de um outro e muda apenas a Idade
var angeloComNovaIdade = angelo with { Idade = 35 };
Console.WriteLine(angeloComNovaIdade);
```

Agora, respira... vai com calma...

```csharp
// Gerado pelo compilador
[System.Runtime.CompilerServices.NullableContext(1)]
[System.Runtime.CompilerServices.Nullable(0)]
public class Pessoa : IEquatable<Pessoa>
{
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly string <Nome>k__BackingField;

    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly int <Idade>k__BackingField;

    [CompilerGenerated]
    protected virtual Type EqualityContract
    {
        [CompilerGenerated]
        get
        {
            return typeof(Pessoa);
        }
    }

    public string Nome
    {
        [CompilerGenerated]
        get
        {
            return <Nome>k__BackingField;
        }
        [CompilerGenerated]
        init
        {
            <Nome>k__BackingField = value;
        }
    }

    public int Idade
    {
        [CompilerGenerated]
        get
        {
            return <Idade>k__BackingField;
        }
        [CompilerGenerated]
        init
        {
            <Idade>k__BackingField = value;
        }
    }

    public Pessoa(string Nome, int Idade)
    {
        <Nome>k__BackingField = Nome;
        <Idade>k__BackingField = Idade;
        base..ctor();
    }

    [CompilerGenerated]
    public override string ToString()
    {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.Append("Pessoa");
        stringBuilder.Append(" { ");
        if (PrintMembers(stringBuilder))
        {
            stringBuilder.Append(' ');
        }
        stringBuilder.Append('}');
        return stringBuilder.ToString();
    }

    [CompilerGenerated]
    protected virtual bool PrintMembers(StringBuilder builder)
    {
        RuntimeHelpers.EnsureSufficientExecutionStack();
        builder.Append("Nome = ");
        builder.Append((object)Nome);
        builder.Append(", Idade = ");
        builder.Append(Idade.ToString());
        return true;
    }

    [System.Runtime.CompilerServices.NullableContext(2)]
    [CompilerGenerated]
    public static bool operator !=(Pessoa left, Pessoa right)
    {
        return !(left == right);
    }

    [System.Runtime.CompilerServices.NullableContext(2)]
    [CompilerGenerated]
    public static bool operator ==(Pessoa left, Pessoa right)
    {
        return (object)left == right || ((object)left != null && left.Equals(right));
    }

    [CompilerGenerated]
    public override int GetHashCode()
    {
        return (EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(<Nome>k__BackingField)) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(<Idade>k__BackingField);
    }

    [System.Runtime.CompilerServices.NullableContext(2)]
    [CompilerGenerated]
    public override bool Equals(object obj)
    {
        return Equals(obj as Pessoa);
    }

    [System.Runtime.CompilerServices.NullableContext(2)]
    [CompilerGenerated]
    public virtual bool Equals(Pessoa other)
    {
        return (object)this == other || ((object)other != null && EqualityContract == other.EqualityContract && EqualityComparer<string>.Default.Equals(<Nome>k__BackingField, other.<Nome>k__BackingField) && EqualityComparer<int>.Default.Equals(<Idade>k__BackingField, other.<Idade>k__BackingField));
    }

    [CompilerGenerated]
    public virtual Pessoa <Clone>$()
    {
        return new Pessoa(this);
    }

    [CompilerGenerated]
    protected Pessoa(Pessoa original)
    {
        <Nome>k__BackingField = original.<Nome>k__BackingField;
        <Idade>k__BackingField = original.<Idade>k__BackingField;
    }

    [CompilerGenerated]
    public void Deconstruct(out string Nome, out int Idade)
    {
        Nome = this.Nome;
        Idade = this.Idade;
    }
}

Pessoa pessoa = new Pessoa("Angelo", 39);
Console.WriteLine(pessoa);
Pessoa pessoa2 = pessoa.<Clone>$();
pessoa2.Idade = 35;
Pessoa value = pessoa2;
Console.WriteLine(value);
```

Novamente aqui temos o compilador trabalhando a todo vapor. Ele criou uma classe `Pessoa` com as propriedades `Nome` e `Idade` somente leitura (sem os `set`). No construtor ele recebe esses valores e preenche os campos. Isso é como a gente fazia antes para ter uma classe imutável.

Porém ele não pára por ai. Ele reescreve os operadores de comparação `!=` e `==` (sim, é possível fazer isso!!) e implementa a interface `IEquatable<Pessoa>`. **Isso vai garantir que dois objetos `Pessoa`, com os mesmos valores, sejam tratados como iguais**.

Além disso temos um método chamado `<Clone>$`, que vai servir para, vejam só, clonar o objeto. Ele vai ser chamado quando for necessário criar um novo objeto a partir de um objeto (do mesmo tipo) já existente - que é o caso do `var angeloComNovaIdade = angelo with { Idade = 35 };`.

O compilador ainda faz o favor de criar o método `Deconstruct`. E pra que serve esse método? Para podermos fazer algo como:

```csharp
// Escrito por mim

var angelo = new Pessoa("Angelo", 39);
var (nome, idade) = angelo;
        
Console.WriteLine(nome);
Console.WriteLine(idade);
```

```csharp
// Gerado pelo compilador
string Nome;
int Idade;
new Pessoa("Angelo", 39).Deconstruct(out Nome, out Idade);
string value = Nome;
int value2 = Idade;
Console.WriteLine(value);
Console.WriteLine(value2);
```

Já pensou ter que fazer tudo isso na mão só pra ter um objeto imutável? Acho que agora dá pra entender os benefícios do Acúcar Sintático :)

---

Quer explorar mais como o compilador do csharp resolve determinados códigos e saber como eu obtive o retorno do compilador? Use o [https://sharplab.io/](https://sharplab.io/).

Nesse site é possível escrever um código csharp e ver o [IL](https://en.wikipedia.org/wiki/Common_Intermediate_Language) gerado, e a partir desse IL o site consegue recriar o csharp, dessa vez com todos os códigos adicionados no momento da compilação. Recomendo fortemente que você teste o uso do _async/await_. Com certeza vai se surpreender! Aliás, ~~quero escrever~~ [escrevi um post só falando sobre isso](https://dev.to/angelobelchior/por-debaixo-do-capo-asyncawait-e-as-magicas-do-compilador-csharp-28ol).

E aí, tem algum açúcar sintático que faltou aqui? Faltaram vários!!! Posta ai nos comentários!

Era isso. :)