0Pricing
C# Academy · Lección

Trimming y limitaciones de la reflexión

Gestione las advertencias de trimming, use [DynamicallyAccessedMembers] y sustituya la reflexión en tiempo de ejecución por alternativas generadas desde el código fuente.

Trimming y limitaciones de la reflexión es una lección gratuita de C# Academy en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de C# Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de C# Academy incluye 4 lecciones en total.

¿Qué es el trimming de IL?

El trimming de IL es una optimización realizada durante la publicación que elimina tipos, métodos y ensamblados no utilizados de la salida. El resultado es un ejecutable más pequeño. Se habilita automáticamente para Native AOT y para las publicaciones autocontenidas en un solo archivo.

Habilitar el trimming

Habilite el trimming en el archivo del proyecto. Puede controlar el modo de trimming desde partial (solo elimina ensamblados no utilizados) hasta full (elimina miembros no utilizados dentro de los ensamblados).

// .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

Cómo funciona el trimmer

El trimmer realiza un análisis de accesibilidad a partir de los puntos de entrada (Main, servicios registrados y atributos). Se elimina todo el código al que no se pueda llegar desde esas raíces.

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

Reflexión y trimming

La reflexión es la principal fuente de incompatibilidades con el trimming. El trimmer no puede analizar estáticamente a qué tipos accederá mediante Type.GetType() o 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] indica al trimmer que conserve miembros específicos a los que sabe que se accederá mediante reflexión.

// 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() { }

Anotación DynamicallyAccessedMembers

[DynamicallyAccessedMembers] anota parámetros, campos y propiedades para indicar al trimmer a qué miembros de un Type se accederá mediante reflexión durante la ejecución.

// 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 con [RequiresUnreferencedCode] los métodos que dependan fundamentalmente de la reflexión. Esto muestra advertencias a los autores de las llamadas para que sepan que el código podría fallar al aplicar trimming.

// 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 seguras para el trimming

Muchos patrones que utilizan mucha reflexión tienen alternativas seguras para el trimming en las versiones modernas de .NET. Prefiera estas alternativas para evitar problemas de trimming.

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

Descriptor XML de trimming

Utilice un descriptor raíz XML para conservar espacios de nombres o ensamblados completos que el trimmer no pueda analizar, como las bibliotecas de terceros que utilizan mucha reflexión.

<!-- 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: solucionar advertencias de trimming

Un flujo de trabajo práctico para resolver las advertencias del trimmer en una 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) { /* ... */ }

Comprobación rápida

¿Qué indica el atributo [DynamicallyAccessedMembers] al trimmer de IL?

Recapitulación: limitaciones del trimming y la reflexión

Conclusiones clave:

  • El trimming elimina el código inaccesible durante la publicación; la reflexión interrumpe este análisis
  • Habilítelo con <PublishTrimmed>true</PublishTrimmed>; utilice TrimMode=full para obtener el máximo ahorro
  • [DynamicDependency]: conserva explícitamente miembros específicos para la reflexión
  • [DynamicallyAccessedMembers]: anota parámetros de tipo Type para que el trimmer sepa qué debe conservar
  • [RequiresUnreferencedCode]: advierte a los autores de las llamadas que un método no es seguro para el trimming
  • Prefiera generadores de código fuente e interfaces en lugar de patrones que utilicen mucha reflexión

Preguntas frecuentes

¿La lección «Trimming y limitaciones de la reflexión» es gratis?

Sí — el texto completo de «Trimming y limitaciones de la reflexión» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de C# Academy, actualiza a CoddyKit PRO. El curso de C# Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Trimming y limitaciones de la reflexión»?

Gestione las advertencias de trimming, use [DynamicallyAccessedMembers] y sustituya la reflexión en tiempo de ejecución por alternativas generadas desde el código fuente. Practicas C# Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar C# Academy?

No se requiere experiencia previa. C# Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.

¿Cuánto tiempo toma la lección «Trimming y limitaciones de la reflexión»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de C# Academy?

Sí. Cada lección de C# Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Compilación Native AOT
  2. Trimming y limitaciones de la reflexión
  3. ReadyToRun y compilación por niveles
  4. Benchmarking con BenchmarkDotNet
← Volver a C# Academy