C# Academy · Aula

Limitações de corte e reflexão

Trate avisos de corte, use [DynamicallyAccessedMembers] e substitua a reflexão em tempo de execução por alternativas geradas durante a compilação.

Aula 2 de 412 etapas

Limitações de corte e reflexão é uma aula grátis de C# Academy no CoddyKit. Esta é a aula 2 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de C# Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de C# Academy inclui 4 aulas no total.

O que é o corte de IL

O corte de IL é uma otimização realizada no momento da publicação que remove tipos, métodos e assemblies não utilizados da saída. O resultado é um executável menor. Ele é habilitado automaticamente para Native AOT e publicações autocontidas em um único arquivo.

Habilitando o corte

Habilite o corte no arquivo do projeto. Você pode controlar o modo de corte de partial (corta apenas assemblies não utilizados) a full (corta membros não utilizados dentro dos assemblies).

// .csproj
<PropertyGroup>
  <!-- Enable trimming on publish -->
  <PublishTrimmed>true</PublishTrimmed>

  <!-- full: aggressive — trims unused members within assemblies
       partial: conservative — only removes unused assemblies -->
  <TrimMode>full</TrimMode>

  <!-- Treat all trimmer warnings as errors (recommended for CI) -->
  <TrimmerRootAssemblies>MyApp</TrimmerRootAssemblies>
  <SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
</PropertyGroup>

// Publish:
dotnet publish -c Release -r linux-x64 --self-contained

Como funciona a ferramenta de corte

A ferramenta de corte realiza uma análise de alcançabilidade começando pelos pontos de entrada (Main, serviços registrados e atributos). Qualquer código que não possa ser alcançado a partir dessas raízes é removido.

// Example: only MyApp.Program.Main is a root
// Code flow:
//   Main → WebApplication.Run → MapGet → MyHandler
//   Everything else is trimmed

// What gets KEPT:
class MyHandler { public string Handle() => "ok"; }

// What gets TRIMMED (if not reached):
class UnusedService { public void DoWork() { } }

// Problem: reflection can access UnusedService at runtime
// But the trimmer can't know that at compile time
// → UnusedService is trimmed → MissingMethodException at runtime

Reflexão e corte

A reflexão é a principal fonte de incompatibilidades com o corte. A ferramenta de corte não consegue analisar estaticamente quais tipos serão acessados por meio de Type.GetType() ou Activator.CreateInstance().

// PROBLEMATIC: type name comes from config at runtime
var typeName = config["Plugin:Type"]!;
var type = Type.GetType(typeName);      // type may have been trimmed!
var instance = Activator.CreateInstance(type!); // MissingMethodException

// PROBLEMATIC: LINQ expressions with reflection
var query = dbContext.Set<T>()          // T discovered via reflection
    .Where(BuildExpression<T>("Name", "Alice"));

// PROBLEMATIC: attribute scanning
var handlers = Assembly.GetExecutingAssembly()
    .GetTypes()
    .Where(t => t.HasCustomAttribute<HandlerAttribute>());
// All handler types may be trimmed away

Atributo DynamicDependency

[DynamicDependency] informa à ferramenta de corte que membros específicos, que você sabe que serão acessados por reflexão, devem ser preservados.

// Keep all public constructors of MyPlugin:
[DynamicDependency(
    DynamicallyAccessedMemberTypes.PublicConstructors,
    typeof(MyPlugin))]
public static IPlugin CreatePlugin()
    => (IPlugin)Activator.CreateInstance(typeof(MyPlugin))!;

// Keep a specific method by name:
[DynamicDependency("ProcessOrder", typeof(OrderHandler))]
public static void Bootstrap() { }

// Keep everything on a type (use sparingly):
[DynamicDependency(
    DynamicallyAccessedMemberTypes.All,
    typeof(LegacyReflectionHelper))]
public static void EnsurePreserved() { }

Anotação DynamicallyAccessedMembers

[DynamicallyAccessedMembers] anota parâmetros, campos e propriedades para informar à ferramenta de corte quais membros de um Type serão acessados por reflexão em tempo de execução.

// Annotate a method parameter:
public void Register(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
    Type serviceType)
{
    // trimmer knows to preserve public constructors of whatever Type is passed
    var instance = Activator.CreateInstance(serviceType)!;
}

// Annotate a generic parameter:
public static T Create<
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
    T>() where T : new()
    => new T();

// Annotate a field:
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
private Type _modelType = typeof(OrderModel);

RequiresUnreferencedCode

Marque com [RequiresUnreferencedCode] os métodos que dependem fundamentalmente de reflexão. Isso exibe avisos para os chamadores, para que saibam que o código pode falhar quando for submetido ao corte.

// Mark a method that uses reflection internally:
[RequiresUnreferencedCode("Uses Type.GetType() — not safe for trimming")]
public static object LoadPlugin(string typeName)
{
    var type = Type.GetType(typeName)!
        ?? throw new InvalidOperationException($"Type {typeName} not found");
    return Activator.CreateInstance(type)!;
}

// Callers will see a trimmer warning:
// IL2026: Members decorated with [RequiresUnreferencedCode]
// need their callers to be decorated with the same attribute
// or be suppressed with [UnconditionalSuppressMessage]

// The warning reminds callers to use a trim-safe alternative
// or add a DynamicDependency for the types they expect

Alternativas compatíveis com o corte

Muitos padrões que usam muita reflexão têm alternativas compatíveis com o corte no .NET moderno. Prefira essas alternativas para evitar problemas de corte.

// INSTEAD OF: Type.GetType(name) + Activator.CreateInstance
// USE: registered factories or switch expressions:
public IHandler CreateHandler(string name) => name switch
{
    "order" => new OrderHandler(),
    "email" => new EmailHandler(),
    _       => throw new ArgumentException($"Unknown: {name}")
};

// INSTEAD OF: reflection-based JSON serialization
// USE: source-generated JsonSerializerContext

// INSTEAD OF: attribute scanning via Assembly.GetTypes()
// USE: compile-time source generators

// INSTEAD OF: dynamic proxy libraries (Castle, DispatchProxy)
// USE: source-generated interceptors (.NET 8+)

XML do descritor de corte

Use um descritor XML de raízes para preservar namespaces ou assemblies inteiros que a ferramenta de corte não consegue analisar — por exemplo, bibliotecas de terceiros que usam muita reflexão.

<!-- TrimmerRoots.xml -->
<linker>
  <!-- Keep everything in LegacyLib -->
  <assembly fullname="LegacyLib">
    <type fullname="LegacyLib.*" preserve="all" />
  </assembly>

  <!-- Keep a specific type -->
  <assembly fullname="MyApp">
    <type fullname="MyApp.Plugins.PluginLoader" preserve="all" />
  </assembly>
</linker>

// Reference in .csproj:
<ItemGroup>
  <TrimmerRootDescriptor Include="TrimmerRoots.xml" />
</ItemGroup>

Caso real: corrigindo avisos de corte

Um fluxo de trabalho prático para resolver avisos da ferramenta de corte em uma base de código real.

// Step 1: enable analyzer during development
// .csproj:
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>

// Step 2: build and read warnings:
// warning IL2026: 'MyMapper.MapDynamic(Type)' uses
// [RequiresUnreferencedCode] via 'Type.GetMethod'

// Step 3: fix the root cause:
// BEFORE:
public void MapDynamic(Type t)
{
    var method = t.GetMethod("MapFrom")!; // IL2026
    method.Invoke(null, null);
}

// AFTER: use an interface instead of reflection
public void Map(IMapper mapper) => mapper.MapFrom();

// Step 4: if fix is not feasible, add annotation and document:
[RequiresUnreferencedCode("Uses reflection: ensure target types are preserved")]
public void MapDynamic(Type t) { /* ... */ }

Verificação rápida

O que o atributo [DynamicallyAccessedMembers] informa à ferramenta de corte de IL?

Recapitulação: limitações do corte e da reflexão

Principais conclusões:

  • O corte remove código inalcançável no momento da publicação — a reflexão interrompe essa análise
  • Habilite com <PublishTrimmed>true</PublishTrimmed>; use TrimMode=full para obter a maior redução possível
  • [DynamicDependency]: preserva explicitamente membros específicos para reflexão
  • [DynamicallyAccessedMembers]: anota parâmetros Type para que a ferramenta de corte saiba o que preservar
  • [RequiresUnreferencedCode]: avisa aos chamadores que um método não é compatível com o corte
  • Prefira geradores de código-fonte e interfaces a padrões que usam muita reflexão
Grátis para começar

Aprenda C# com um tutor de IA — grátis

Escreva e execute código real no seu navegador, obtenha ajuda instantânea de um tutor de IA 24/7 e continue de onde parou na web ou no app.

Cursos
93
Aulas
346

Perguntas Frequentes

A aula “Limitações de corte e reflexão” é grátis?

Sim — o texto completo de “Limitações de corte e reflexão” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de C# Academy, atualize para CoddyKit PRO. O curso de C# Academy inclui 4 aulas no total.

O que vou aprender em “Limitações de corte e reflexão”?

Trate avisos de corte, use [DynamicallyAccessedMembers] e substitua a reflexão em tempo de execução por alternativas geradas durante a compilação. Você pratica C# Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar C# Academy?

Nenhuma experiência prévia é necessária. C# Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 2 de 4.

Quanto tempo leva a aula “Limitações de corte e reflexão”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de C# Academy?

Sim. Cada aula de C# Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Compilação Native AOT
  2. Limitações de corte e reflexão
  3. ReadyToRun e compilação em camadas
  4. Benchmarking com BenchmarkDotNet
← Voltar para C# Academy