-
Notifications
You must be signed in to change notification settings - Fork 1
Compiler pt BR
🌐 Esta página em: English · Português
O compilador é o componente central que torna possível a mágica do eQuantic.UI. Ele transforma a semântica do C# em código TypeScript eficiente e legível.
A correção é sustentada por uma suíte diferencial de conformidade: o mesmo C# é avaliado no .NET e no Bun embarcado, e as duas respostas têm que concordar. Qualquer coisa que o compilador não consiga traduzir fielmente é um erro de build com localização (os diagnósticos EQ2xxx), nunca um erro silencioso.
Para manter o código do browser honesto, o compilador impõe fronteiras estritas, inspiradas no Next.js (a divisão servidor/cliente) e no Flutter (restrições), e as valida antes de emitir JS:
-
Componentes cliente (
StatefulComponent/StatelessComponent): lógica de interface, gestão de estado,System.Linq, tipos básicos (string,int,DateTime). -
Proibido no cliente:
System.IO,System.Net.Httpdireto,.Wait()bloqueante. UmFile.ReadAllText()no corpo de um componente é erro de build. -
A ponte: a busca de dados passa por métodos anotados com
[ServerAction](estilo RPC). Veja Segurança e Server Actions.
O TypeScriptEmitter é o ponto de entrada para gerar os arquivos .ts. Ele organiza os imports, define as classes, e usa o CSharpToJsConverter para converter os corpos dos métodos.
Despacha cada nó do Roslyn para uma strategy — uma por construto (BinaryExpressionStrategy,
IfStatementStrategy, …) — e devolve IR, não texto. Statements sempre constroem um
JsStatement. Uma strategy de expressão que já atravessou constrói um JsExpr
(IExpressionIrStrategy); uma que ainda devolve texto entra como nó opaco, byte-idêntico ao que
sempre produziu. Essa fronteira é o que deixa a migração andar uma strategy por vez, e
tests/eQuantic.UI.Compiler.Tests/Coverage/ir-migration.baseline.txt é a lista das strategies em
texto — só encolhe, e uma strategy nova nasce no IR.
Gera Source Maps V3 padrão com codificação Base64 VLQ, mapeando o JavaScript/TypeScript gerado de volta às linhas originais do .cs ou .eqx.
O conversor pergunta ao modelo semântico antes de adivinhar. Um membro, um local, um parâmetro, um
estático — cada um emite a partir do seu símbolo (this.name, Widget.name, um nome puro), e
Console.WriteLine vira console.log porque o símbolo diz System.Console. Heurísticas por nome
(um sublinhado inicial, uma propriedade capitalizada) só são legítimas onde o modelo não pode ser
consultado — um trecho sem modelo, um nó reescrito por uma strategy. Com um modelo autoritativo, um
nome na árvore que não vincula é erro de build (EQ2006), nunca uma tradução adivinhada.
Desde 0.2.0-preview.36
CodeGen/Ir/ é uma árvore pequena com um escritor por nível — JsExpr → JsStatement →
JsClassMember → JsClass → JsModule — e os escritores são donos de tudo o que uma strategy
nunca deve escrever à mão:
-
Parênteses vêm de precedência e associatividade, nunca de um template. Antes disto,
f ?? g && gsaía literal: o C# não precisa de parênteses ali, o JavaScript recusa a mistura nua, e o bundle inteiro deixava de parsear. -
Avaliação única: um
JsTemplatediz o que computa ({0} === {0}.normalize()) e o escritor liga uma parte usada mais de uma vez exatamente uma vez — nome puro ou literal é inlined, leitura de membro não (um getter pode contar). -
Layout: um statement por linha, blocos indentados (
JsLayout.Pretty);Compactreproduz o antigo mundo de string byte a byte, e é assim que cada passo da migração é provado. Uma classe tem uma regra só de layout — linha em branco antes de um membro com corpo, campos contíguos — e um módulo são os seus imports como registosJsImport, uma linha em branco, o corpo. -
O emitter (
TypeScriptEmitter) decide o que um módulo contém e entrega nós ao builder; não monta texto.
Duas redes seguram tudo isso: os pins dos componentes (o módulo gerado de cada componente
compartilhado, byte a byte — uma mudança de layout tem de ser só-whitespace contra eles) e a suíte
de conformance, que executa cada forma traduzida dos dois lados e compara as respostas. Os gêmeos
gerados em src/eQuantic.UI.Runtime/src/shared/components são fixados, tipados e testados — não
lintados: código gerado responde ao seu escritor, não a um guia de estilo.
Hoje o compilador suporta uma ampla gama de construções do C#:
-
Expressões: aritméticas, lógicas, ternárias, interpolação de string, null-coalescing (
??), acesso condicional (?.,?[]) -
Controle de fluxo:
if,switch,for,foreach,while,do-while,break,continue,throw - Padrões modernos: suporte completo a padrões recursivos, de propriedade, posicionais, relacionais e lógicos (C# 9.0 - 12.0)
-
Gestão de recursos: suporte a instruções
usinge declaraçõesusing var -
Exceções: suporte completo a
try-catch-finallye a instruçõesthrow(Exception → Error) -
Índices e intervalos: suporte ao operador de índice a partir do fim (
array[^1]→array[array.length - 1]) -
Métodos de string: métodos de instância (
Split,Replace,StartsWith,EndsWith,Contains,Substring,IndexOf,LastIndexOf,PadLeft,PadRight,Trim,TrimStart,TrimEnd,ToUpper,ToLower,ToUpperInvariant,ToLowerInvariant,Insert,Remove,ToCharArray) e métodos estáticos (IsNullOrEmpty,IsNullOrWhiteSpace,Join,Concat,Compare,Equals,Format) -
Métodos numéricos:
int.Parse,double.Parse,float.Parse,decimal.Parse,long.Parse,int.TryParse,double.TryParse -
Métodos de List:
Add,AddRange,Insert,InsertRange,Remove,RemoveAt,RemoveRange,RemoveAll,Clear,IndexOf,LastIndexOf,Find,FindIndex,FindLast,FindLastIndex,FindAll,Exists,TrueForAll,Sort,ForEach,GetRange,CopyTo,BinarySearch -
Métodos estáticos de Array: suporte completo aos métodos estáticos de Array
-
Array.Sort(array)→array.sort()- ordena o array no lugar -
Array.Sort(array, comparison)→array.sort(comparison)- ordena com comparador próprio -
Array.Reverse(array)→array.reverse()- inverte o array no lugar -
Array.Find(array, predicate)→array.find(predicate)- acha o primeiro elemento que casa -
Array.FindIndex(array, predicate)→array.findIndex(predicate)- acha o índice do primeiro que casa -
Array.FindAll(array, predicate)→array.filter(predicate)- acha todos os elementos que casam -
Array.IndexOf(array, value)→array.indexOf(value)- acha o índice de um valor -
Array.LastIndexOf(array, value)→array.lastIndexOf(value)- acha o último índice de um valor -
Array.Exists(array, predicate)→array.some(predicate)- confere se algum elemento casa -
Array.TrueForAll(array, predicate)→array.every(predicate)- confere se todos os elementos casam -
Array.Clear(array)→array.splice(0)- limpa todos os elementos -
Array.Resize(ref array, size)→array.length = size- redimensiona o array
-
-
Métodos de enum: suporte completo às operações de enum
-
Enum.Parse<T>(string)→parseEnum(value, EnumType)(sem diferenciar maiúsculas) -
Enum.TryParse<T>(string, out var result)→(result = parseEnum(value, EnumType), result !== undefined) -
Enum.GetValues<T>()→Object.values(EnumType)- pega todos os valores do enum -
Enum.GetNames<T>()→Object.keys(EnumType)- pega todos os nomes dos membros -
Enum.IsDefined(typeof(T), value)→(EnumType[value] !== undefined)- valida um valor de enum
-
-
Métodos de Dictionary: suporte completo a Dictionary/IDictionary
-
ContainsKey(key)→(key in dict)- confere se a chave existe -
TryGetValue(key, out var value)→(value = dict[key]) !== undefined- obtenção segura do valor -
Add(key, value)→dict[key] = value- acrescenta ou atualiza a entrada -
Remove(key)→delete dict[key]- remove a entrada -
Clear()→Object.keys(dict).forEach(k => delete dict[k])- remove todas as entradas -
Keys(propriedade) →Object.keys(dict)- pega todas as chaves como array -
Values(propriedade) →Object.values(dict)- pega todos os valores como array
-
-
LINQ: conversão direta dos métodos LINQ para os equivalentes em JS:
-
Projeção:
Select→map,SelectMany→flatMap -
Filtragem:
Where→filter,Distinct→[...new Set()] -
Ordenação:
OrderBy/OrderByDescending→sort,Reverse→[...arr].reverse() -
Particionamento:
Skip→slice(n),Take→slice(0, n) -
Elemento:
First/FirstOrDefault→find/[0],Last/LastOrDefault→arr[arr.length-1],Single/SingleOrDefault→find/[0] -
Quantificadores:
Any→some/length > 0,All→every,Contains→includes -
Agregação:
Count→length/filter().length,Sum→reduce((a,b) => a+b, 0),Average→reduce()/length,Min→Math.min(...),Max→Math.max(...) -
Operações de conjunto:
-
Concat(other)→[...source, ...other]- concatena duas sequências -
Union(other)→[...new Set([...source, ...other])]- elementos únicos das duas sequências -
Intersect(other)→[...new Set(source)].filter(x => other.includes(x))- elementos em comum -
Except(other)→[...new Set(source)].filter(x => !other.includes(x))- elementos na origem mas não na outra
-
-
Filtragem por tipo:
-
Cast<T>()→ repasse (o JavaScript tem tipagem dinâmica) -
OfType<T>()→filter(x => typeof x === 'type')para primitivos,filter(x => x instanceof Type)para objetos
-
-
Projeção:
-
Async/await: mapeamento de
TaskparaPromisee suporte nativo aawait. -
Operadores modernos do C#: suporte aos operadores e palavras-chave modernos
-
Atribuição null-coalescing:
x ??= value→x ?? (x = value)- atribui só se for nulo/indefinido -
Operador nameof:
nameof(variable)→'variable'- pega o nome como string em tempo de compilação -
Palavra-chave default:
default(int)→0,default(string)→null,default→undefined- pega o valor padrão do tipo
-
Atribuição null-coalescing:
Dois padrões são implícitos no C# e ausentes no JavaScript. Os dois foram emitidos como nada por um tempo, e os dois falham TARDE, não em tempo de build, e não onde está a causa.
Desde 0.2.0-preview.22
Um campo de tipo de valor é zero tenha alguém escrito = 0 ou não. No cliente ele era undefined, e
os dois não são o mesmo valor. As leituras sobrevivem por sorte enquanto forem TESTES (undefined > 0
é falso, que é o que 0 teria dito), e aí a primeira ARITMÉTICA o transforma em NaN. Um
Math.max(width, undefined) chega à folha de estilo como width:NaNpx, uma regra que o parser de CSS
descarta inteira: a classe é computada, hasheada, emitida, posta no elemento, e não faz nada.
Isso aparece só em páginas RENDERIZADAS no cliente, nunca no SSR nem numa carga direta, porque o servidor computa a mesma propriedade em C#, onde ela era 0 o tempo todo. Então os dois alvos discordam sobre um campo e a página que prova isso é a que ninguém recarrega.
Todo tipo de valor não anulável agora carrega o padrão dele (0, false, o membro zero de um enum).
Os anuláveis não, porque ali null É a resposta do C#, e inventar um zero seria a mesma divergência
apontando para o outro lado.
Desde 0.2.0-preview.21
public sealed class TocEntry(Action<string, bool> onSeen, string id) : StatelessComponent
{
public override VisualNode Build(ComponentContext context) =>
new InView(Heading(id), visible => onSeen(id, visible)); // this.onSeen, this.id
}O Roslyn modela a captura como um IParameterSymbol, então todo lugar que pergunta "isto é um
parâmetro?" responde sim sobre algo que se comporta como campo. Emitido cru ele compila, a página
renderiza, e o ReferenceError chega quando o callback finalmente dispara, aparecendo de dentro do
reconciliador como um TypeError sobre outra coisa completamente diferente.
Um componente não é o único C# de que uma página precisa. O modelo de documento por trás de um editor, uma pequena máquina de estados, um parser: nenhum deles é componente, e todos eles têm que rodar nos dois alvos. Eles transpilam do mesmo jeito, como módulos próprios, e as regras abaixo são o que torna a emissão CONFERIDA em vez de meramente presente.
line[start..end] → line.slice(start, end)
line[2..] → line.slice(2)
line[..^1] → line.slice(0, -1)
line[..^n] → $eq.slice(line, 0, false, n, true)O último formato é o que o JavaScript não consegue dizer diretamente: ^0 significa o FIM, enquanto
slice(0, -0) é slice(0, 0), que é vazio. Qualquer coisa que não seja um literal positivo depois de
^ resolve, portanto, contra o comprimento do jeito que o Index.GetOffset faz. Um Range guardado
como VALOR é reportado (EQ2004): nada do outro lado recebe um, e indexar no ponto de uso é para o que
ele serve.
O JavaScript não tem nenhum dos dois. Um método que os declara devolve um OBJETO (o valor dele sob $,
cada out e ref sob o nome deles) e o corpo dele se move para dentro de um closure para que todo return
ali continue significando o que significava. O ponto de chamada desembrulha com uma arrow, o que
funciona em qualquer posição de expressão, incluindo dentro de um if:
var next = document.Replace(range, text, out var caret);let caret: any;
let next = ($o => (caret = $o.caret, $o.$))(document.replace(range, text));O out sai da lista de parâmetros do JS (ele não é passado PARA DENTRO); o ref fica, porque é lido
antes de ser escrito. out _ não atribui nada.
new List<T>(x) significa duas coisas opostas dependendo do que x é, e só o construtor resolvido
consegue dizer qual: new List<string>(other.Count) é uma lista vazia dimensionada de antemão,
new List<string>(other) é uma cópia. O primeiro emite [], o segundo [...other].
Um char do C# em + - * / % promove a int e computa na unidade de código, enquanto um char
transpilado é uma string de comprimento 1. Quando o tipo do RESULTADO é numérico, os operandos char
rebaixam para unidades de código (literais constantes dobram para o número; expressões leem
charCodeAt(0)), então text[i] - '0' é o dígito e 'A' + col é um número, exatamente como no .NET. O
(char)numeric rebaixa para String.fromCharCode. char + string continua sendo concatenação: o tipo
do resultado é string, então o ramo numérico nunca o vê.
O JavaScript não tem decimal nem inteiro de 64 bits, portanto o fio envia-os como strings — um
long como "9007199254740993", um decimal como "0.1", a família de datas em texto ISO. O
compilador sabe o tipo C# de cada campo de estado e de cada retorno de Server Action, e escreve esse
conhecimento no gémeo como um mapa static $hydration; o runtime coage UMA vez, onde o valor chega,
em vez de cada sítio de uso coagir defensivamente. Um record nomeia o seu próprio mapa, uma lista é
[spec], os valores de um dicionário são { dict: spec }, e um tuplo é posicional
({ tuple: [...] }) porque atravessa como array. Nada é emitido onde hidratar seria a identidade,
que é a maioria dos campos.
new int[0].SingleOrDefault() é 0 no .NET, não null, e o mesmo vale para FirstOrDefault,
LastOrDefault, ElementAtOrDefault e DefaultIfEmpty. Um campo declarado sem inicializador toma
o mesmo default: um int é 0, um bool false, um long 0n, e um ENUM o seu membro de valor zero —
que deste lado é uma string com o NOME do membro, portanto um campo por definir renderizaria nada.
Uma tabela só responde pelos dois casos, por símbolo, para que um tipo alcançado por alias
(using Amount = decimal;) não tenha resposta diferente do próprio tipo.
xs.OrderBy(a).OrderBy(b) ordena por b. A ordenação anterior não permanece como chave primária —
sobrevive apenas como o desempate que uma ordenação estável lhe dá — portanto é um resultado
diferente de xs.OrderBy(a).ThenBy(b) sempre que b tem empates que a desfaria. Os dois
atravessam fielmente: um OrderBy encadeado ordena primeiro a sua fonte e ordena isso, e o
ThenBy compõe a sua chave na mesma comparação.
Math.round(32.5) é 33 no JavaScript e 32 no .NET, que arredonda a metade para o par. Em todo o
sítio onde o runtime espelha um MathF.Round usa a regra do .NET, para que um valor calculado no
browser seja o valor que o servidor calculou — é isso que mantém a altura de linha de uma escala
tipográfica, e qualquer layout derivado dela, idêntica na hidratação em vez de meio pixel ao lado.
A maioria dos operadores LINQ é uma forma só — Where é filter, Aggregate é reduce com os
argumentos trocados — portanto vivem como entradas numa tabela indexada por operador e número de
argumentos, com o gate escrito uma vez e o escritor do IR a pontuar e a ligar qualquer recetor usado
duas vezes. Um operador que tem de RACIOCINAR guarda estratégia própria: a família OrDefault lê o
tipo do elemento, o Sum escolhe semente por ele, Cast e OfType testam tipos, Contains escolhe
entre igualdade de identidade e estrutural. A mesma forma vale para a BCL numérica, onde a superfície
moderna (Double.AcosPi e família) é uma tabela de templates em vez de um método cada.
Onde essas tabelas não têm entrada, a chamada é ERRO de build a nomear o membro, nunca um palpite:
um nome emitido sem nada por trás é um ReferenceError no carregamento, e uma página que nunca
renderiza.
Um byte do C# passando de 255 envolve; um número do JavaScript continua contando. O compilador lê o
tipo do RESULTADO de cada + - * << (e de ++, -- e das formas compostas) e assenta o valor onde o
C# assentaria: byte, sbyte, short, ushort e uint SEMPRE envolvem (& 0xFF, << 16 >> 16,
>>> 0 — valores empacotados e hashes dependem disso, então h *= 16777619 num uint é o passo FNV
que é no .NET, via Math.imul). int e long só envolvem onde você escreveu unchecked
(unchecked(a * a) é 0 para 65536; um long envolve via BigInt.asIntN), porque senão todo i + 1
comum numa UI carregaria um | 0 por um overflow que é bug em qualquer outro lugar — então um
int.MaxValue + 1 sem mais continua a contagem do double, um limite documentado. Um contexto checked
— o bloco, a expressão ou a configuração do projeto inteiro, lida do IsChecked da árvore vinculada e
não da sintaxe — lança overflow exatamente onde o C# lança. Um resultado float é arredondado para
precisão simples (Math.fround) e imprime como o decimal mais curto que lê de volta o mesmo single,
então 0.1f + 0.2f é "0.3" dos dois lados. char++ avança o caractere; um enum em aritmética
(day + 1, a.CompareTo(b)) computa no valor por trás do nome do membro.
A sintaxe nunca mostra uma conversão implícita — int i = c com um char, Twice(c), a[c],
long l = n — e uma regra escrita contra a sintaxe só cobre as formas de que o autor se lembrou.
O compilador lê-as da árvore vinculada do Roslyn: depois de cada expressão traduzida, a conversão
em que a árvore vinculada a embrulha é aplicada (ValueFlow), em todo sítio onde o C# a aplica —
inicializadores, argumentos, índices, retornos, comparações. Um char promovido a número vira a
sua unidade de código (uma constante dobra: 'A' + col é 65 + col); um int a fluir para um
long vira BigInt (1 → 1n, e é por isso que TimeSpan.FromSeconds(90) emite 90n: a
sobrecarga do .NET 9 recebe um long). Dois chars comparados continuam caracteres — o JavaScript
ordena strings de 1 caractere pelas mesmas unidades de código — e um operador implícito definido
pelo utilizador passa o valor adiante, porque os wrappers do framework (SizeValue, Index,
ColorToken) são a sua primitiva deste lado.
Um valor a caminho de TEXTO assenta da mesma forma: embalado numa concatenação, operando string
de uma, ou buraco simples de interpolação, imprime como o C# imprime — null como nada, um bool
como "True", um enum pelo nome do membro — s += flag incluído, que nenhuma regra sintática vira.
O JavaScript não sobrecarrega +, então um record ou struct que você declara com operadores leva
cada um no gémeo como método estático, e todo sítio em que a árvore vinculada mostra um operador o
chama: a + b é Money.opAdd(a, b), -m é Money.opNegate(m) (o - unário e o binário têm nome
por aridade, então nunca colidem), m += other é m = Money.opAdd(m, other). Conversões também:
implicit operator Money(int v) vira Money.fromInt, chamado onde o C# converte — uma
declaração, um argumento, o operando de um compound — e explicit operator int(Money m) vira
Money.toInt, chamado pelo cast. Só para tipos no seu código: um wrapper do framework como
SizeValue ou Index é a sua primitiva deste lado, e os operadores dele passam o valor adiante.
O ELEMENTO de um foreach converte da mesma forma, um item de cada vez: foreach (long l in ints)
faz de cada int um BigInt, foreach (int code in chars) de cada char a sua unidade de código,
foreach (Money m in ints) chama a conversão do tipo — o laço liga um $l cru e declara l a partir dele.
Uma DECLARAÇÃO using (using var r = …;) é dona do resto do bloco: o que se segue corre dentro de
um try cujo finally descarta r — depois de o valor de retorno ser tomado, quando o corpo lança, em
ordem inversa quando vários partilham um bloco — e await using aguarda o disposeAsync.
Um Dictionary transpilado com chave primitiva é um objeto simples, e não iterável, então um foreach
sobre ele (e o new List<KeyValuePair<,>>(dict)) rebaixa por $eq.entries(obj, numericKeys): pares que
desconstroem como [key, value] E respondem .key/.value (as duas formas de consumo do C#), com as
chaves numéricas restauradas como números (o Object.entries as transforma em string, e uma chave
virada string transformaria o próximo key + 1 em concatenação). Dicionários com chave de record/struct
mantêm o rebaixamento para valueMap; só chaves primitivas tomam este caminho.
new List<KeyValuePair<int,float>>(…) não consegue anotar o let dele com o nome C# cru
(KeyValuePair[] não nomeia nada em TS); itens genéricos deixam a anotação para a inferência.
Os módulos da biblioteca compartilhada se importam por um barril só, então um campo estático
inicializado no momento da avaliação do módulo pode ver a classe de outro módulo como undefined.
Qualquer inicializador que nomeie outro módulo vira um getter preguiçoso respaldado por um slot privado,
que também é a tradução fiel, já que o C# inicializa os statics de um tipo no primeiro uso. Literais
puros continuam campos.
O .ts emitido é checado por tipos (essa é a segunda das duas camadas), então uma assinatura nunca pode
introduzir um nome que o módulo não consiga resolver:
| C# | TypeScript |
|---|---|
| um enum |
string, já que a representação em execução é o nome do membro |
| uma interface sem gêmeo emitido | any |
IReadOnlyList<(char, char)> |
[string, string][] |
Action<T>? |
((t: T) => void) | null, entre parênteses, ou a união liga ao retorno |
char |
string |
| um nome que nada consegue verificar |
any, porque um tipo errado é pior que um aberto |
Os records carregam as mesmas regras, mais os campos estáticos deles e as propriedades computadas (um record é um valor com COMPORTAMENTO, não só os membros posicionais dele).
Tanto o conjunto transpilado quanto o barril de exports do runtime são gerados a partir do diretório de código, nunca de uma lista mantida à mão, então a biblioteca embarcada nunca consegue se afastar do código a partir do qual ela é construída.
Código C#:
private void Increment() {
Count++;
if (Count > 10) Console.WriteLine("Max reached");
}Saída TypeScript:
increment() {
this.count++;
if (this.count > 10) console.log("Max reached");
}Código C#:
public enum OrderStatus { Pending, Processing, Shipped, Delivered }
private void HandleStatusChange(string input)
{
// Faz o parse do enum a partir de uma string (sem diferenciar maiúsculas)
if (Enum.TryParse<OrderStatus>(input, out var status))
{
Console.WriteLine($"Status changed to: {status}");
}
// Pega todos os valores do enum para um dropdown
var allStatuses = Enum.GetValues<OrderStatus>();
foreach (var s in allStatuses)
{
Console.WriteLine($"Available status: {s}");
}
// Valida um valor de enum
if (Enum.IsDefined(typeof(OrderStatus), "Shipped"))
{
Console.WriteLine("Valid status");
}
}Saída TypeScript:
handleStatusChange(input: string) {
// Parse com TryParse
if ((status = parseEnum(input, OrderStatus), status !== undefined)) {
console.log(`Status changed to: ${status}`);
}
// Pega todos os valores
const allStatuses = Object.values(OrderStatus);
for (const s of allStatuses) {
console.log(`Available status: ${s}`);
}
// Valida
if ((OrderStatus['Shipped'] !== undefined)) {
console.log('Valid status');
}
}Código C#:
private Dictionary<string, int> _settings = new();
private void ManageSettings()
{
// Acrescenta entradas
_settings.Add("timeout", 5000);
_settings.Add("retries", 3);
// Confere a existência
if (_settings.ContainsKey("timeout"))
{
var timeout = _settings["timeout"];
Console.WriteLine($"Timeout: {timeout}");
}
// Obtenção segura
if (_settings.TryGetValue("maxItems", out var max))
{
Console.WriteLine($"Max: {max}");
}
// Itera as chaves
foreach (var key in _settings.Keys)
{
Console.WriteLine($"{key} = {_settings[key]}");
}
// Limpa tudo
_settings.Clear();
}Saída TypeScript:
private _settings: Record<string, number> = {};
manageSettings() {
// Acrescenta entradas
this._settings['timeout'] = 5000;
this._settings['retries'] = 3;
// Confere a existência
if (('timeout' in this._settings)) {
const timeout = this._settings['timeout'];
console.log(`Timeout: ${timeout}`);
}
// Obtenção segura
if ((max = this._settings['maxItems']) !== undefined) {
console.log(`Max: ${max}`);
}
// Itera as chaves
for (const key of Object.keys(this._settings)) {
console.log(`${key} = ${this._settings[key]}`);
}
// Limpa tudo
Object.keys(this._settings).forEach(k => delete this._settings[k]);
}Código C#:
private void ProcessCollections()
{
var list1 = new[] { 1, 2, 3, 4 };
var list2 = new[] { 3, 4, 5, 6 };
// Concatena as duas listas
var combined = list1.Concat(list2);
// Resultado: [1, 2, 3, 4, 3, 4, 5, 6]
// Union - elementos únicos das duas
var union = list1.Union(list2);
// Resultado: [1, 2, 3, 4, 5, 6]
// Intersect - elementos em comum
var common = list1.Intersect(list2);
// Resultado: [3, 4]
// Except - elementos na list1 mas não na list2
var difference = list1.Except(list2);
// Resultado: [1, 2]
// Filtragem complexa com operações de conjunto
var activeUsers = GetActiveUsers();
var premiumUsers = GetPremiumUsers();
// Usuários que são ativos E premium
var activePremium = activeUsers.Intersect(premiumUsers);
// Usuários que são ativos mas NÃO premium
var activeFree = activeUsers.Except(premiumUsers);
}Saída TypeScript:
processCollections() {
const list1 = [1, 2, 3, 4];
const list2 = [3, 4, 5, 6];
// Concatena
const combined = [...list1, ...list2];
// Union (com Set para remover duplicatas)
const union = [...new Set([...list1, ...list2])];
// Intersect (elementos em comum)
const common = [...new Set(list1)].filter(x => list2.includes(x));
// Except (diferença)
const difference = [...new Set(list1)].filter(x => !list2.includes(x));
// Filtragem complexa
const activeUsers = this.getActiveUsers();
const premiumUsers = this.getPremiumUsers();
const activePremium = [...new Set(activeUsers)].filter(x => premiumUsers.includes(x));
const activeFree = [...new Set(activeUsers)].filter(x => !premiumUsers.includes(x));
}Código C#:
private void ProcessArrayOperations()
{
var numbers = new[] { 5, 2, 8, 1, 9 };
var items = new[] { "apple", "banana", "cherry" };
// Ordena o array no lugar
Array.Sort(numbers);
// Resultado: [1, 2, 5, 8, 9]
// Ordena com comparação própria
Array.Sort(items, (a, b) => b.Length - a.Length);
// Resultado: ["banana", "cherry", "apple"]
// Inverte o array
Array.Reverse(numbers);
// Resultado: [9, 8, 5, 2, 1]
// Operações de busca
var firstEven = Array.Find(numbers, n => n % 2 == 0);
var firstEvenIndex = Array.FindIndex(numbers, n => n % 2 == 0);
var allEvens = Array.FindAll(numbers, n => n % 2 == 0);
// Operações de procura
var index = Array.IndexOf(numbers, 5);
var lastIndex = Array.LastIndexOf(numbers, 5);
// Operações de verificação
var hasEven = Array.Exists(numbers, n => n % 2 == 0);
var allPositive = Array.TrueForAll(numbers, n => n > 0);
// Limpa e redimensiona
Array.Clear(numbers);
Array.Resize(ref items, 5); // Expande para 5 elementos
}Saída TypeScript:
processArrayOperations() {
const numbers = [5, 2, 8, 1, 9];
const items = ["apple", "banana", "cherry"];
// Ordena
numbers.sort();
// Ordena com comparação
items.sort((a, b) => b.length - a.length);
// Inverte
numbers.reverse();
// Operações de busca
const firstEven = numbers.find(n => n % 2 == 0);
const firstEvenIndex = numbers.findIndex(n => n % 2 == 0);
const allEvens = numbers.filter(n => n % 2 == 0);
// Operações de procura
const index = numbers.indexOf(5);
const lastIndex = numbers.lastIndexOf(5);
// Operações de verificação
const hasEven = numbers.some(n => n % 2 == 0);
const allPositive = numbers.every(n => n > 0);
// Limpa e redimensiona
numbers.splice(0);
items.length = 5;
}Código C#:
private void FilterByType()
{
// Coleção de tipos mistos
object[] mixed = new object[] { 1, "hello", 2, "world", 3.14, true };
// Cast<T>() - assume que todos os elementos são do tipo T (repasse em JS)
var assumedStrings = mixed.Cast<string>();
// OfType<T>() - filtra só os elementos do tipo T
var onlyStrings = mixed.OfType<string>();
// Resultado: ["hello", "world"]
var onlyNumbers = mixed.OfType<int>();
// Resultado: [1, 2]
// Funciona com classes próprias também
var shapes = new object[] { new Circle(), new Square(), new Circle() };
var circles = shapes.OfType<Circle>();
// Resultado: [Circle, Circle]
// Filtragem de tipos primitivos
var primitives = new object[] { 1, "text", 2.5, true, null };
var strings = primitives.OfType<string>(); // ["text"]
var numbers = primitives.OfType<double>(); // [1, 2.5]
var booleans = primitives.OfType<bool>(); // [true]
}Saída TypeScript:
filterByType() {
// Coleção de tipos mistos
const mixed = [1, "hello", 2, "world", 3.14, true];
// Cast - repasse (o JS tem tipagem dinâmica)
const assumedStrings = mixed;
// OfType - filtra por typeof para primitivos
const onlyStrings = mixed.filter(x => typeof x === 'string');
// Resultado: ["hello", "world"]
const onlyNumbers = mixed.filter(x => typeof x === 'number');
// Resultado: [1, 2, 3.14]
// OfType - filtra por instanceof para objetos
const shapes = [new Circle(), new Square(), new Circle()];
const circles = shapes.filter(x => x instanceof Circle);
// Resultado: [Circle, Circle]
// Filtragem de primitivos
const primitives = [1, "text", 2.5, true, null];
const strings = primitives.filter(x => typeof x === 'string'); // ["text"]
const numbers = primitives.filter(x => typeof x === 'number'); // [1, 2.5]
const booleans = primitives.filter(x => typeof x === 'boolean'); // [true]
}Código C#:
private void DemonstrateModernOperators()
{
// Atribuição null-coalescing (??=)
string? cachedData = null;
cachedData ??= LoadDataFromDatabase(); // Só carrega se for nulo
cachedData ??= "Default"; // Não executa, já foi atribuído
// Atribuição null-coalescing em propriedade
if (user.Settings ??= new Settings())
{
Console.WriteLine("Created new settings");
}
// Operador nameof (útil para binding de propriedade, validação)
var propertyName = nameof(user.Email);
Console.WriteLine($"Validating {propertyName}"); // "Validating Email"
var methodName = nameof(ProcessOrder);
LogAction(methodName); // "ProcessOrder"
// Palavra-chave default - valores padrão com tipo seguro
int count = default(int); // 0
string? text = default(string); // null
bool flag = default(bool); // false
DateTime date = default(DateTime); // 1/1/0001 12:00:00 AM
// Literal default (contextual)
int number = default; // 0 (inferido do tipo)
ProcessData(default); // passa o valor padrão do tipo do parâmetro
}
private void ProcessData(int value = default)
{
// value cai para 0 se não for fornecido
}Saída TypeScript:
demonstrateModernOperators() {
// Atribuição null-coalescing
let cachedData = null;
cachedData ?? (cachedData = this.loadDataFromDatabase());
cachedData ?? (cachedData = 'Default');
// Atribuição em propriedade
if (this.user.settings ?? (this.user.settings = new Settings())) {
console.log('Created new settings');
}
// Operador nameof
const propertyName = 'Email';
console.log(`Validating ${propertyName}`);
const methodName = 'ProcessOrder';
this.logAction(methodName);
// Palavra-chave default
let count = 0;
let text = null;
let flag = false;
let date = null;
// Literal default
let number = undefined;
this.processData(undefined);
}
processData(value = 0) {
// value cai para 0
}Código C#:
private void StringManipulation()
{
var text = " Hello World ";
// Aparando
var trimmed = text.Trim(); // "Hello World"
var leftTrim = text.TrimStart(); // "Hello World "
var rightTrim = text.TrimEnd(); // " Hello World"
// Conversão de caixa
var upper = text.ToUpper(); // " HELLO WORLD "
var lower = text.ToLower(); // " hello world "
var upperInv = text.ToUpperInvariant(); // " HELLO WORLD "
var lowerInv = text.ToLowerInvariant(); // " hello world "
// Encadeando métodos
var clean = text.Trim().ToLower().Replace("world", "everyone");
// Resultado: "hello everyone"
}Saída TypeScript:
stringManipulation() {
const text = " Hello World ";
// Aparando
const trimmed = text.trim();
const leftTrim = text.trimStart();
const rightTrim = text.trimEnd();
// Conversão de caixa
const upper = text.toUpperCase();
const lower = text.toLowerCase();
const upperInv = text.toUpperCase();
const lowerInv = text.toLowerCase();
// Encadeando
const clean = text.trim().toLowerCase().replaceAll("world", "everyone");
}🌐 English · Português
🏁 Comece aqui
📱 Write-once
- Componentes write-once
- Superfície declarativa
- Motor Photon
- Design System
- Capacidades
- Armazenamento
- Formulários
- Editor de código
- Markdown
- Mermaid
- Renderização de Email
🏗️ Arquitetura
⚙️ Compilação
- Compilador
- Avaliação em tempo de compilação
- Recursos C# suportados
- Resolução de tipos externos
- Fluxo de build
- Diagnósticos
⚡ Runtime
🔌 Servidor
🎨 Ecossistema
🚀 Desenvolvimento