Nota
O acceso a esta páxina require autorización. Pode tentar iniciar sesión ou modificar os directorios.
O acceso a esta páxina require autorización. Pode tentar modificar os directorios.
En este artículo se explican los conceptos básicos detrás del análisis de recorte para ayudarle a comprender por qué determinados patrones de código generan advertencias y cómo hacer que el código sea compatible con el recorte. Comprender estos conceptos le ayudará a tomar decisiones fundamentadas al abordar advertencias de recorte en lugar de simplemente "distribuir atributos alrededor para silenciar las herramientas".
Cómo analiza el optimizador el código
El recortador realiza análisis estático en tiempo de publicado para determinar qué código usa tu aplicación. Se inicia desde puntos de entrada conocidos (como el método Main) y sigue las rutas de código a través de la aplicación.
Lo que la cortadora puede entender
El optimizador se destaca al analizar patrones de código directos y visibles en tiempo de compilación:
// The trimmer CAN understand these patterns:
var date = new DateTime();
date.AddDays(1); // Direct method call - trimmer knows AddDays is used
var list = new List<string>();
list.Add("hello"); // Generic method call - trimmer knows List<string>.Add is used
string result = MyUtility.Process("data"); // Direct static method call
En estos ejemplos, el recortador puede seguir la ruta de código y marcar DateTime.AddDays, List<string>.Add y MyUtility.Process como código utilizado que debe mantenerse en la aplicación final.
Lo que el recortador no puede entender
El recortador tiene dificultades con las operaciones dinámicas en las que el destino de una operación no se conoce hasta el tiempo de ejecución.
// The trimmer CANNOT fully understand these patterns:
Type type = Type.GetType(Console.ReadLine()); // Type name from user input
type.GetMethod("SomeMethod"); // Which method? On which type?
object obj = GetSomeObject();
obj.GetType().GetProperties(); // What type will obj be at runtime?
Assembly asm = Assembly.LoadFrom(pluginPath); // What's in this assembly?
En estos ejemplos, el recortador no tiene forma de saber:
- El tipo que introducirá el usuario
- ¿Qué tipo devuelve
GetSomeObject()? - ¿Qué código existe en el ensamblado cargado dinámicamente?
Este es el problema fundamental que abordan las advertencias.
Problema de reflexión
La reflexión permite que el código inspeccione e invoque tipos y miembros dinámicamente en tiempo de ejecución. Esto es eficaz, pero crea un desafío para el análisis estático.
¿Por qué la reflexión interrumpe la poda?
Considere este ejemplo:
void PrintMethodNames(Type type)
{
foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}
}
// Called somewhere in the app
PrintMethodNames(typeof(DateTime));
Desde la perspectiva del recortador:
- Se llama
type.GetMethods(). - No sabe qué
typeserá (es un parámetro). - No puede determinar qué métodos de tipos deben conservarse.
- Sin instrucciones, podría quitar métodos de
DateTime, rompiendo el código.
Por lo tanto, el recortador genera una advertencia en este código.
Descripción de DynamicallyAccessedMembers
DynamicallyAccessedMembersAttribute resuelve el problema de reflexión mediante la creación de un contrato explícito entre el autor de la llamada y el método llamado.
El propósito fundamental
DynamicallyAccessedMembers indica al recortador: "Este parámetro (o campo o valor devuelto) contendrá un Type que necesita miembros específicos que deben conservarse porque la reflexión se usará para acceder a ellos".
Ejemplo concreto
Vamos a corregir el ejemplo anterior:
void PrintMethodNames(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}
}
// When this is called...
PrintMethodNames(typeof(DateTime));
Ahora el recortador entiende lo siguiente:
-
PrintMethodNamesrequiere que se mantenga el parámetroPublicMethods. - El punto de llamada pasa
typeof(DateTime). - Por lo tanto, se deben mantener los métodos públicos de
DateTime.
El atributo crea un requisito que fluye hacia atrás desde el uso de la reflexión hasta el origen del Type valor.
Es un contrato, no una sugerencia
Esto es fundamental para comprender: DynamicallyAccessedMembers no es solo documentación. El recortador hace cumplir este contrato.
Analogía con restricciones de tipo genérico
Si está familiarizado con las restricciones de tipo genérico, DynamicallyAccessedMembers funciona de forma similar. Al igual que las restricciones genéricas fluyen a través del código:
void Process<T>(T value) where T : IDisposable
{
value.Dispose(); // OK because constraint guarantees IDisposable
}
void CallProcess<T>(T value) where T : IDisposable
{
Process(value); // OK - constraint satisfied
}
void CallProcessBroken<T>(T value)
{
Process(value); // ERROR - T doesn't have IDisposable constraint
}
DynamicallyAccessedMembers crea requisitos similares que fluyen a través del código:
void UseReflection([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
type.GetMethods(); // OK because annotation guarantees methods are preserved
}
void PassType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
UseReflection(type); // OK - requirement satisfied
}
void PassTypeBroken(Type type)
{
UseReflection(type); // WARNING - type doesn't have required annotation
}
Ambos crean contratos que deben cumplirse y generan errores o advertencias cuando no se puede satisfacer el contrato.
Cómo se aplica el contrato
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
Type GetTypeForProcessing()
{
return typeof(DateTime); // OK - trimmer will preserve DateTime's public methods
}
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
Type GetTypeFromInput()
{
// WARNING: The trimmer can't verify that the type from GetType()
// will have its public methods preserved
return Type.GetType(Console.ReadLine());
}
Si no puede cumplir el contrato (como en el segundo ejemplo), recibirá una advertencia.
Descripción de RequireUnreferencedCode
Algunos patrones de código simplemente no se pueden analizar estáticamente. En estos casos, use RequiresUnreferencedCodeAttribute.
Cuándo usar RequiresUnreferencedCode
Use el RequiresUnreferencedCodeAttribute atributo cuando:
- El patrón de reflexión es fundamentalmente dinámico: cargar ensamblados o tipos mediante nombres de cadena desde orígenes externos.
- La complejidad es demasiado alta para anotar: código que usa la reflexión en formas complejas controladas por datos.
-
Está usando la generación de código en tiempo de ejecución: tecnologías como System.Reflection.Emit o la
dynamicpalabra clave .
Ejemplo:
[RequiresUnreferencedCode("Plugin loading is not compatible with trimming")]
void LoadPlugin(string pluginPath)
{
Assembly pluginAssembly = Assembly.LoadFrom(pluginPath);
// Plugin assemblies aren't known at publish time
// This fundamentally cannot be made trim-compatible
}
Propósito del atributo
RequiresUnreferencedCode tiene dos propósitos:
- Suprime los avisos dentro del método: el optimizador no analizará ni advertirá sobre el uso de la reflexión.
- Crea advertencias en los sitios de llamada: cualquier código que llama a este método obtiene una advertencia.
Esta "elevación" de la advertencia está diseñada para dar a los desarrolladores visibilidad de las rutas de acceso de código incompatibles con recorte.
Escribir mensajes buenos
El mensaje debe ayudar a los desarrolladores a comprender sus opciones:
// ❌ Not helpful
[RequiresUnreferencedCode("Uses reflection")]
// ✅ Helpful - explains what's incompatible and suggests alternatives
[RequiresUnreferencedCode("Plugin loading is not compatible with trimming. Consider using a source generator for known plugins instead")]
Cómo fluyen los requisitos a través del código
Comprender cómo propagan los requisitos le ayuda a saber dónde agregar atributos.
Los requisitos fluyen hacia atrás
Los requisitos fluyen desde dónde se usa la reflexión hasta dónde Type se origina:
void CallChain()
{
// Step 1: Source of the Type value
ProcessData<DateTime>(); // ← Requirement ends here
}
void ProcessData<T>()
{
// Step 2: Type flows through generic parameter
var type = typeof(T);
DisplayInfo(type); // ← Requirement flows back through here
}
void DisplayInfo(Type type)
{
// Step 3: Reflection creates the requirement
type.GetMethods(); // ← Requirement starts here
}
Para que este recorte sea compatible, debe anotar la cadena:
void ProcessData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
{
var type = typeof(T);
DisplayInfo(type);
}
void DisplayInfo(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
type.GetMethods();
}
Ahora, los flujos de requisitos: GetMethods() requiere PublicMethods → type parámetro necesita PublicMethods → T genérico necesita PublicMethods → DateTime necesita PublicMethods conservado.
Los requisitos fluyen a través del almacenamiento
Los requisitos también fluyen a través de campos y propiedades:
class TypeHolder
{
// This field will hold Types that need PublicMethods preserved
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
private Type _typeToProcess;
public void SetType<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
{
_typeToProcess = typeof(T); // OK - requirement satisfied
}
public void Process()
{
_typeToProcess.GetMethods(); // OK - field is annotated
}
}
Elección del enfoque adecuado
Cuando encuentre código que necesite reflexión, siga este árbol de decisión:
1. ¿Puedes evitar la reflexión?
La mejor solución es evitar la reflexión siempre que sea posible:
// ❌ Uses reflection
void Process(Type type)
{
var instance = Activator.CreateInstance(type);
}
// ✅ Uses compile-time generics instead
void Process<T>() where T : new()
{
var instance = new T();
}
2. ¿Se conoce el tipo en tiempo de compilación?
Si la reflexión es necesaria, pero se conocen los tipos, use DynamicallyAccessedMembers:
// ✅ Trim-compatible
void Serialize<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(T obj)
{
foreach (var prop in typeof(T).GetProperties())
{
// Serialize property
}
}
3. ¿El patrón es fundamentalmente dinámico?
Si los tipos realmente no se conocen hasta el tiempo de ejecución, use RequiresUnreferencedCode:
// ✅ Documented as trim-incompatible
[RequiresUnreferencedCode("Dynamic type loading is not compatible with trimming")]
void ProcessTypeByName(string typeName)
{
var type = Type.GetType(typeName);
// Work with type
}
Patrones y soluciones comunes
Patrón: métodos de fábrica
// Problem: Creating instances from Type parameter
object CreateInstance(Type type)
{
return Activator.CreateInstance(type);
}
// Solution: Specify constructor requirements
object CreateInstance(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type)
{
return Activator.CreateInstance(type);
}
Patrón: Sistemas de complementos
// Problem: Loading unknown assemblies at runtime
[RequiresUnreferencedCode("Plugin loading is not trim-compatible. Plugins must be known at compile time.")]
void LoadPlugins(string pluginDirectory)
{
foreach (var file in Directory.GetFiles(pluginDirectory, "*.dll"))
{
Assembly.LoadFrom(file);
}
}
// Better solution: Known plugins with source generation
// Use source generators to create plugin registration code at compile time
Conclusiones clave
- El recortador usa el análisis estático - solo puede comprender las rutas de acceso de código visibles en el momento de la compilación.
- La reflexión rompe el análisis estático: el recortador no puede determinar qué se accederá mediante reflexión en tiempo de ejecución.
- DynamicallyAccessedMembers crea contratos : indica al recortador lo que se debe conservar.
-
Los requisitos fluyen hacia atrás — desde el uso de la reflexión hasta el origen del
Typevalor. - RequiresUnreferencedCode documenta incompatibilidades - úselo cuando el código no se pueda analizar.
- Los atributos no son solo sugerencias : el recortador aplica contratos y genera advertencias cuando no se pueden cumplir.
Pasos siguientes
- Corrección de advertencias de recorte : aplique estos conceptos para resolver advertencias en el código.
- Preparación de bibliotecas para el recorte : hacer que las bibliotecas sean compatibles con el recorte
- Referencia de advertencia de recorte : información detallada sobre advertencias específicas