Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungWhat is Programming And How i can Enjoy it?(24.09.2026 um 11:54 Uhr)
Sichere ProgrammierungYou Don't Need Adobe Commerce Cloud to Survive Black Friday(24.09.2026 um 11:55 Uhr)
Malware / Trojaner / VirenBeyond Lazarus: Organization of DPRK cyber capabilities(24.09.2026 um 11:59 Uhr)
Malware / Trojaner / VirenBeyond Lazarus: Organization of DPRK Cyber Capabilities(24.09.2026 um 11:59 Uhr)
Malware / Trojaner / VirenThe fake worker threat and the rise of human infiltration(24.09.2026 um 11:59 Uhr)
Malware / Trojaner / VirenPolinRider Spreads Through Compromised GitHub Accounts and Packagist(24.09.2026 um 11:59 Uhr)
Malware / Trojaner / VirenWeaselBiscuit Strips BeaverTail and OtterCookie Down to Essentials(24.09.2026 um 11:59 Uhr)
Sichere ProgrammierungWhat is Programming And How i can Enjoy it?(24.09.2026 um 11:54 Uhr)
Sichere ProgrammierungYou Don't Need Adobe Commerce Cloud to Survive Black Friday(24.09.2026 um 11:55 Uhr)
Malware / Trojaner / VirenBeyond Lazarus: Organization of DPRK cyber capabilities(24.09.2026 um 11:59 Uhr)
Malware / Trojaner / VirenBeyond Lazarus: Organization of DPRK Cyber Capabilities(24.09.2026 um 11:59 Uhr)
Malware / Trojaner / VirenThe fake worker threat and the rise of human infiltration(24.09.2026 um 11:59 Uhr)
Malware / Trojaner / VirenPolinRider Spreads Through Compromised GitHub Accounts and Packagist(24.09.2026 um 11:59 Uhr)
Malware / Trojaner / VirenWeaselBiscuit Strips BeaverTail and OtterCookie Down to Essentials(24.09.2026 um 11:59 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

C# Lowering

O processo de compilação Você sabe como é o processo que seu código em C# passa para ser executado? Muita se fala sobre o processo de compilar o código, que transforma o código C# em IL, mas existe um passo anterior que transforma C# em .…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!




O processo de compilação



Você sabe como é o processo que seu código em C# passa para ser executado?

Muita se fala sobre o processo de compilar o código, que transforma o código C# em IL, mas existe um passo anterior que transforma C# em ... C#.



Esse processo é conhecido como Lowering.



Ele é responsável por um processo que vai "otimizar" o código para o compilador e vai trocar algumas facilidades da linguagem por comandos que são entendidos mais facilmente pelo compilador.

Isso tem ajudado muito na adição de funcionalidades no C#, como é o caso do record ou até mesmo como agora são permitidos top-level statements.





Exemplos de Lowering



O record é utilizado para representar um objeto de valores imutáveis.

Porém isso já poderia ser feito usando classes com parâmetros no construtor e propriedades readonly.

O que o record faz é facilitar essa implementação comum para o desenvolvedor.



Por exemplo, quando você escreve:




public record Record(int ID);






O que o compilador vai receber é:





[NullableContext(1)]
[Nullable(0)]
public class Record : IEquatable<Record>
{
[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly int <ID>k__BackingField;

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

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

public Record(int ID)
{
<ID>k__BackingField = ID;
base..ctor();
}

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

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

[NullableContext(2)]
[CompilerGenerated]
public static bool operator !=(Record left, Record right)
{
return !(left == right);
}

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

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

[NullableContext(2)]
[CompilerGenerated]
public override bool Equals(object obj)
{
return Equals(obj as Record);
}

[NullableContext(2)]
[CompilerGenerated]
public virtual bool Equals(Record other)
{
return (object)this == other || ((object)other != null && EqualityContract == other.EqualityContract && EqualityComparer<int>.Default.Equals(<ID>k__BackingField, other.<ID>k__BackingField));
}

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

[CompilerGenerated]
protected Record(Record original)
{
<ID>k__BackingField = original.<ID>k__BackingField;
}

[CompilerGenerated]
public void Deconstruct(out int ID)
{
ID = this.ID;
}
}







Muito mais complexo, não é?



Outro exemplo, que está na linguagem tem um bom tempo é o uso de foreach:




Record[] records = [new Record(1), new Record(2), new Record(3)];

foreach (var x in records)
{
Console.WriteLine(x.ID);
}






Se torna:




private static void <Main>$(string[] args)
{
Record[] array = new Record[3];
array[0] = new Record(1);
array[1] = new Record(2);
array[2] = new Record(3);
Record[] array2 = array;
Record[] array3 = array2;
int num = 0;
while (num < array3.Length)
{
Record record = array3[num];
Console.WriteLine(record.ID);
num++;
}
}






No exemplo ainda conseguimos ver como a inicialização de listas, que foi adicionada mais recentemente, funciona por trás dos panos.



Outro ponto importante é que nesse caso podemos ver a diferença de código gerado quando se usa tipos diferentes.

O mesmo código anterior, usando List ao invés de Array gera a seguinte saída:




int num = 3;
List<Record> list = new List<Record>(num);
CollectionsMarshal.SetCount(list, num);
Span<Record> span = CollectionsMarshal.AsSpan(list);
int num2 = 0;
span[num2] = new Record(1);
num2++;
span[num2] = new Record(2);
num2++;
span[num2] = new Record(3);
num2++;
List<Record> list2 = list;
List<Record>.Enumerator enumerator = list2.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
Record current = enumerator.Current;
Console.WriteLine(current.ID);
}
}
finally
{
((IDisposable)enumerator).Dispose();
}






Tudo isso permitiu que várias facilitações e funcionalidades fossem feitas na linguagem ao longo dos anos, sem alterações muito significativas no compilador. Acredito que uma das maiores foi o acréscimo de async/await na linguagem, que facilitou muito a programação assíncrona em comparação com o modelo anterior.



Além dessas facilidades de escrita e funcionalidades, também são implementadas melhorias de performance. Saber que esse processo existe, e como ver o código gerado por ele, pode facilitar o nosso entendimento de alguns comportamentos que nosso código exibe, como podemos ver a seguir.






Comportamentos inesperados



O que me motivou esse post foi encontrar recentemente um comportamento inesperado em um método extremamente simples e que foi compreendido com mais facilidade através da análise do código gerado pelo lowering.



Vamos levar a seguinte classe em consideração:




public class Example
{
public double ValueA {get;set;}
public int ValueB {get;set;}

public object GetByIndex(int index)
=> index switch
{
0 => ValueA,
1 => ValueB,
_ => throw new Exception("")
};
}






Essa implementação foi necessária por conta de um tipo de serialização que leva em consideração a ordem dos campos, e foi levemente alterada para simplificar o exemplo.



Como esse código é usado por uma biblioteca de serialização o tipo do valor retornado é bem importante (mesmo que esteja sofrendo boxing pelo tipo de retorno ser object).



Agora a dúvida.

Caso o seguinte código seja executado, o que será exibido no console?




var example = new Example
{
ValueA = 2.0,
ValueB = 50
};

Console.WriteLine(example.GetByIndex(0).GetType().FullName);
Console.WriteLine(example.GetByIndex(1).GetType().FullName);






Se você espera que seja




System.Double

System.Int32




Eu e você estamos no mesmo barco.

Porém no barco errado....

O que é exibido é:




System.Double

System.Double




Agora vamos entender porque isso acontece, olhando o código gerado após o lowering para o método GetByIndex:




[NullableContext(1)]
public object GetByIndex(int index)
{
double num;
if (index != 0)
{
if (index != 1)
{
throw new Exception("");
}
num = ValueB;
}
else
{
num = ValueA;
}
return num;
}






Olhando esse código fica claro o motivo de o valor retornado está com o tipo double, agora falta entender o porquê.

Primeiro vamos olhar outros cenários para entender quando isso acontece e quando não acontece.

Quando usamos um switch...case ao invés de um switch expression o resultado é o esperado.



Antes do lowering:




 public object GetByIndex(int index)
{
switch(index)
{
case 0: return ValueA;
case 1: return ValueB;
default: throw new Exception("");
}
}






Após lowering:





[NullableContext(1)]
public object GetByIndex(int index)
{
if (index != 0)
{
if (index == 1)
{
return ValueB;
}
throw new Exception("");
}
return ValueA;
}






O problema também não acontece caso os tipos das propriedades sejam double e decimal:




public class Example
{
public double ValueA {get;set;}
public decimal ValueB {get;set;}

public object GetByIndex(int index)
=> index switch
{
0 => ValueA,
1 => ValueB,
_ => throw new Exception("")
};
}






Após o lowering:




[NullableContext(1)]
public object GetByIndex(int index)
{
if (index != 0)
{
if (index == 1)
{
return ValueB;
}
throw new Exception("");
}
return ValueA;
}






Então o que acarreta na resultado inesperado?



Se olharmos o switch expression inicial, fora do contexto de implementação do método, fica um pouco mais fácil de entender.




int index = 1; 
var result = index switch
{
0 => 2.0,
1 => 1,
_ => throw new Exception("")
};






Como podemos ver, o resultado da switch expression deve ser atribuído a uma variável e para isso o compilador escolhe um tipo em comum entre os retornos de todos as opções existentes. Como qualquer valor do tipo int pode ser representado no tipo double, o valor inteiro é convertido para double.

Caso adicionemos uma nova opção 2 => "outro valor", então o único tipo em comum entre as três opções é object. Desse modo os três valores vão sofrer boxing, e mantêm o tipo original de alguma forma.





O que posso fazer?



Conhecer a existência do processo de lowering e como ele se encaixa como etapa do processo de compilação nos ajuda a ter uma caixa de ferramentas maior na hora de entender e solucionar problemas.



Quer ver como partes do seu código está ficando após o lowering? Pode acessar aqui e ver: https://sharplab.io/



Além disso, se quiser ver um pouco mais:



SOC Incident Playbook: Vulnerability Remediation & Verification
title: Detect Exploitation - C# Lowering
id: 8947e940-2753-4301-9980-663ef9a47c4b
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-24
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-24"
        description = "YARA Signature for "
    strings:
        $str = "C# Lowering" ascii wide
    condition:
        any of them
}
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich C# Lowering.... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten C# Lowering

Thematisch verwandte Begriffe: Lowering · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY Kritische Sicherheitsmeldung
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel TTP ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick