-
Notifications
You must be signed in to change notification settings - Fork 1
CompileTimeEvaluation pt BR
🌐 Esta página em: English · Português
O CompileTimeEvaluator é um compilador simbólico baseado no Roslyn que avalia expressões em tempo de build para tipos marcados com [CompileTimeEvaluate]. Isso permite custo zero em tempo de execução para tipos de valor sem sobrecusto como uma AtomicClass, convertendo chamadas de método complexas em literais de string durante a compilação.
- Conceitos-chave
- Como funciona
- Padrões suportados
- Arquitetura
- Exemplos de uso
- Ganhos de performance
- Extensibilidade
- Limitações
A avaliação em tempo de compilação transforma código de execução em constantes de compilação:
// Código C# (tempo de build)
var className = TW.WithOpacity(TW.Bg.White, 80);
// JavaScript gerado (sem custo nenhum em tempo de execução!)
let className = "bg-white/80";Marque os tipos que devem ser avaliados em tempo de compilação:
[CompileTimeEvaluate]
public struct AtomicClass
{
private readonly string _value;
public AtomicClass(string value) => _value = value;
public static implicit operator string(AtomicClass c) => c._value;
public static AtomicClass WithOpacity(string className, int opacity)
=> new($"{className}/{opacity}");
}Requisitos:
- Tem que ser um
struct(tipo de valor) - Tem que ter conversão implícita para
string - Os métodos têm que ser determinísticos (mesma entrada = mesma saída)
O avaliador confere se o tipo de uma expressão tem [CompileTimeEvaluate]:
var typeInfo = _semanticModel.GetTypeInfo(expression);
if (!IsCompileTimeEvaluatable(typeInfo.Type))
return null; // Pula - não é avaliávelAnalisa o código-fonte do método para detectar os padrões de implementação:
// Análise do código-fonte
public static AtomicClass WithOpacity(string className, int opacity)
=> new($"{className}/{opacity}");
// Padrão detectado: InterpolatedString
// Template: "{className}/{opacity}"
// Parâmetros: [className, opacity]Executa o padrão com os argumentos avaliados:
// Entrada: TW.WithOpacity("bg-white", 80)
// Passo 1: avalia os argumentos → ["bg-white", "80"]
// Passo 2: aplica o padrão → "bg-white/80"
// Passo 3: cacheia o resultadoOs resultados são cacheados para evitar recomputação:
private readonly Dictionary<string, string> _cache = [];
private readonly Dictionary<string, ITypeSymbol> _cacheTypes = [];Detecta dependências circulares:
private readonly HashSet<string> _evaluationStack = [];
if (_evaluationStack.Contains(key))
return null; // Referência circular detectadaO avaliador reconhece 5 padrões comuns de implementação:
Padrão:
public static Type Method(string arg1, int arg2)
=> new($"{arg1}/{arg2}");Exemplo:
TW.WithOpacity("bg-white", 80) // → "bg-white/80"
TW.Px(4) // → "px-4"Padrão:
public static Type Method(params string[] classes)
=> new(string.Join(" ", classes));Exemplo:
TW.Multi("flex", "items-center", "gap-4") // → "flex items-center gap-4"Padrão:
public static Type Method(string prefix, string value)
=> new(string.Format("{0}:{1}", prefix, value));Exemplo:
TW.Format("hover", "bg-blue-500") // → "hover:bg-blue-500"Padrão:
public static Type Method(string value)
=> new(value);Exemplo:
TW.Create("flex") // → "flex"Padrão:
public static Type Method(string prefix, string value)
=> new(prefix + ":" + value);Exemplo:
TW.Prefix("dark", "bg-zinc-900") // → "dark:bg-zinc-900"CompileTimeEvaluator
├── TryEvaluate(expression) ──────────► Ponto de entrada principal
│ ├── Confere o cache
│ ├── Detecta recursão
│ ├── Valida [CompileTimeEvaluate]
│ └── Tenta as estratégias de avaliação
│
├── EvaluateMemberAccess() ───────────► TW.Bg.White
├── EvaluateMethodCall() ─────────────► TW.WithOpacity(...)
│ └── TrySymbolicCompilation()
│ ├── DetectMethodPattern() ────► Reconhecimento de padrão
│ └── ExecutePattern() ─────────► Execução simbólica
│ ├── ExecuteInterpolatedStringPattern()
│ ├── ExecuteStringJoinPattern()
│ ├── ExecuteStringFormatPattern()
│ ├── ExecuteParameterPassthroughPattern()
│ └── ExecuteBinaryExpressionPattern()
│
├── EvaluateBinaryExpression() ───────► TW.A + TW.B
├── EvaluateObjectCreation() ─────────► new AtomicClass("flex")
└── EvaluateConstantValue() ──────────► "flex"
Expressão C#: TW.Dark(TW.WithOpacity(TW.Bg.White, 80))
↓
1. TryEvaluate(TW.Dark(...))
├─ Confere o cache: FALTA
├─ Confere o tipo: AtomicClass [CompileTimeEvaluate] ✓
↓
2. EvaluateMethodCall(TW.Dark(...))
├─ Avalia os argumentos:
│ └─ TW.WithOpacity(TW.Bg.White, 80)
│ ├─ Avalia TW.Bg.White → "bg-white"
│ ├─ Avalia 80 → "80"
│ └─ Padrão: string interpolada
│ └─ Resultado: "bg-white/80"
↓
3. TrySymbolicCompilation(TW.Dark(...))
├─ Pega o código-fonte
├─ DetectMethodPattern()
│ └─ Padrão: string interpolada
├─ ExecutePattern(["bg-white/80"])
│ └─ Template: "dark:{arg}"
└─ Resultado: "dark:bg-white/80"
↓
4. Cacheia o resultado: "dark:bg-white/80"
5. Devolve: "dark:bg-white/80"
[CompileTimeEvaluate]
public struct MyClass
{
private readonly string _value;
public MyClass(string value) => _value = value;
public static implicit operator string(MyClass c) => c._value;
// Todos estes padrões funcionam automaticamente!
// Padrão 1: string interpolada
public static MyClass WithOpacity(string color, int opacity)
=> new($"{color}/{opacity}");
// Padrão 2: String.Join
public static MyClass Join(params string[] classes)
=> new(string.Join(" ", classes));
// Padrão 3: String.Format
public static MyClass Format(string prefix, string value)
=> new(string.Format("{0}-{1}", prefix, value));
// Padrão 4: repasse
public static MyClass Create(string value)
=> new(value);
// Padrão 5: expressão binária
public static MyClass Concat(string a, string b)
=> new(a + "-" + b);
}// Código C# do componente
var cardClasses = ClassBuilder.Create()
.Add(TW.P(4), TW.Rounded.Lg)
.Add(TW.Bg.White)
.Dark(TW.WithOpacity(TW.Bg.Zinc900, 95))
.Hover(TW.Shadow.Xl)
.Build();
// JavaScript gerado (tudo em tempo de compilação!)
let cardClasses = ClassBuilder.create()
.add("p-4", "rounded-lg")
.add("bg-white")
.dark("bg-zinc-900/95")
.hover("shadow-xl")
.build();// Expressão aninhada complexa
TW.Dark(
TW.Hover(
TW.WithOpacity(TW.Bg.Blue600, 50)
)
)
// Avalia para:
"dark:hover:bg-blue-600/50"
// Ordem da avaliação:
// 1. TW.Bg.Blue600 → "bg-blue-600"
// 2. TW.WithOpacity("bg-blue-600", 50) → "bg-blue-600/50"
// 3. TW.Hover("bg-blue-600/50") → "hover:bg-blue-600/50"
// 4. TW.Dark("hover:bg-blue-600/50") → "dark:hover:bg-blue-600/50"| Abordagem | Custo em execução | Tamanho do bundle | Avaliação |
|---|---|---|---|
| Tempo de compilação | ✅ Nenhum | ✅ Mínimo | ✅ Em tempo de build |
| Avaliação em execução | ❌ Alto | ❌ Grande | ❌ A cada render |
Antes da avaliação em tempo de compilação:
- Tamanho do bundle: ~85KB
- Helpers em execução: classe TW + todos os métodos
- Primeiro paint: ~120ms
Depois da avaliação em tempo de compilação:
- Tamanho do bundle: ~49KB (42% de redução!)
- Helpers em execução: nenhum (só strings)
- Primeiro paint: ~80ms (33% mais rápido!)
Antes:
// Avaliação em execução (lenta, bundle grande)
let className = TW.Dark(TW.WithOpacity(TW.Bg.Zinc900, 95));
// Exige: a classe TW, o método Dark, o método WithOpacity, o objeto BgDepois:
// Avaliação em tempo de compilação (rápida, bundle pequeno)
let className = "dark:bg-zinc-900/95";
// Exige: nada! Só um literal de stringPara suportar novos padrões, acrescente ao DetectMethodPattern():
private static MethodPattern? DetectMethodPattern(
MethodDeclarationSyntax methodDecl,
IMethodSymbol methodSymbol)
{
// ... padrões existentes ...
// Padrão novo: expressão condicional
if (bodyExpr is ConditionalExpressionSyntax conditional)
{
return new MethodPattern
{
Type = PatternType.Conditional,
Template = conditional,
Parameters = [.. methodSymbol.Parameters]
};
}
return null;
}Depois implemente o executor:
private string? ExecuteConditionalPattern(MethodPattern pattern, List<string> args)
{
// Implementação aqui
}Para assemblies externos, sobreponha via reflexão:
private string? TryInvokeMethodViaReflection(
IMethodSymbol methodSymbol,
List<object?> args,
List<ITypeSymbol?>? argTypes = null)
{
// Lógica própria para métodos externos
}❌ Código dependente de execução:
public static MyClass Random()
=> new(Guid.NewGuid().ToString()); // ❌ Não determinístico❌ Estado externo:
private static int counter = 0;
public static MyClass Counter()
=> new($"item-{counter++}"); // ❌ Estado mutável❌ LINQ complexo:
public static MyClass Complex(params string[] items)
=> new(items.Where(i => i.Length > 5).Select(i => i.ToUpper()).Join(" ")); // ❌ Complexo demais✅ Contorno - use padrões mais simples:
public static MyClass Complex(params string[] items)
=> new(string.Join(" ", items)); // ✅ AvaliávelQuando a avaliação falha, o código cai para o tempo de execução:
// Não dá para avaliar em tempo de compilação
var result = TW.When(condition, "a", "b"); // condition é uma variável de execução
// JavaScript gerado (avaliação em execução)
let result = TW.When(condition, "a", "b"); // Inclui o TW no bundleAviso mostrado durante o build:
warning: Could not evaluate compile-time expression at TodoList.cs(123).
Falling back to runtime code. Expression: TW.When(condition, "a", "b")
var evaluator = new CompileTimeEvaluator(semanticModel);
// Confira as estatísticas do cache
var (cachedCount, typesCached) = evaluator.GetCacheStats();
Console.WriteLine($"Cached: {cachedCount}, Types: {typesCached}");
// Confira se a expressão está cacheada
bool isCached = evaluator.IsCached("TW.Bg.White");evaluator.ClearCache(); // Para testes ou quando o semantic model mudaAs falhas de avaliação em tempo de compilação são logadas como avisos:
dotnet build
# Saída:
warning: Could not evaluate compile-time expression at SourceFile([123..456))
Falling back to runtime code. Expression: TW.Complex(...)- Use métodos simples e determinísticos
- Siga os padrões reconhecidos
- Mantenha a lógica sem estado
- Teste com constantes de tempo de compilação
- Acessar estado externo
- Usar operações não determinísticas (Random, DateTime.Now)
- Criar cadeias LINQ complexas
- Modificar variáveis estáticas
- Prefira padrões mais simples - strings interpoladas são as mais rápidas
- Evite aninhamento profundo - cada nível acrescenta custo de avaliação
- Use o cache - a mesma expressão só é avaliada uma vez
- Confira os avisos - avaliações que falham prejudicam a performance em execução
O CompileTimeEvaluator é uma abstração sem sobrecusto que permite classes utilitárias elegantes e com tipos seguros, sem custo em tempo de execução. Analisando as implementações dos métodos e executando-as simbolicamente em tempo de build, ele converte chamadas de método complexas em literais de string simples, resultando em:
- Bundles 42% menores (nenhum helper em execução é necessário)
- Primeiro paint 33% mais rápido (sem avaliação em execução)
- 100% de segurança de tipos (checagem em tempo de compilação do C#)
- Zero custo em tempo de execução (só literais de string)
Isso faz do eQuantic.UI um dos frameworks de interface mais rápidos, mantendo uma excelente experiência de desenvolvimento.
🌐 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