Compartir por


Corrección de advertencias de recorte

Al habilitar el recorte en la aplicación, el SDK de .NET realiza análisis estáticos para detectar patrones de código que podrían no ser compatibles con el recorte. Las advertencias de recorte indican posibles problemas que podrían provocar cambios de comportamiento o bloqueos después del recorte.

Una aplicación que usa el recorte no debe generar ninguna advertencia de recorte. Si hay advertencias de recorte, pruebe exhaustivamente la aplicación después del recorte para asegurarse de que no haya ningún cambio de comportamiento.

En este artículo se proporcionan flujos de trabajo prácticos para solucionar las advertencias de recorte. Para obtener una comprensión más profunda de por qué se producen estas advertencias y cómo funciona el recorte, consulte Descripción del análisis de recorte.

Descripción de las categorías de advertencia

Las advertencias de recorte se dividen en dos categorías principales:

  • Código incompatible con el recorte : marcado con RequiresUnreferencedCodeAttribute. El código fundamentalmente no se puede analizar (por ejemplo, la carga dinámica de ensamblajes o complejos patrones de reflexión). El método está marcado como incompatible y los autores de llamadas reciben advertencias.

  • Código con requisitos : anotado con DynamicallyAccessedMembersAttribute. La reflexión se usa, pero los tipos se conocen en tiempo de compilación. Cuando se cumplen los requisitos, el código se vuelve totalmente compatible con el recorte.

Flujo de trabajo: determinación del enfoque correcto

Cuando encuentre una advertencia de recorte, siga estos pasos en orden:

  1. Eliminar reflexión : esta es siempre la mejor opción si es posible.
  2. Usar DynamicallyAccessedMembers : si se conocen los tipos, haga que el código sea compatible con el recorte.
  3. Usar RequireUnreferencedCode : si es realmente dinámico, documente la incompatibilidad.
  4. Suprimir advertencias como último recurso : solo si está seguro de que el código es seguro.

Enfoque 1: Eliminar la reflexión

La mejor solución es evitar la reflexión completamente cuando sea posible. Esto hace que su código sea más rápido y totalmente compatible con trim.

Uso de genéricos en tiempo de compilación

Reemplace las operaciones de tipo en tiempo de ejecución por parámetros genéricos en tiempo de compilación:

// ❌ Before: Uses reflection
void CreateAndProcess(Type type)
{
    var instance = Activator.CreateInstance(type);
    // Process instance...
}

// ✅ After: Uses generics
void CreateAndProcess<T>() where T : new()
{
    var instance = new T();
    // Process instance...
}

Uso de generadores de origen

.NET moderno proporciona generadores de origen para escenarios de reflexión comunes:

Para más información, vea Incompatibilidades conocidas de recorte.

Enfoque 2: Hacer que el recorte de código sea compatible con DynamicallyAccessedMembers

Cuando la reflexión es necesaria, pero los tipos se conocen en tiempo de compilación, use DynamicallyAccessedMembersAttribute para hacer que el código sea compatible con el recorte.

Paso a paso: Anotación del uso de reflexión

Considere este ejemplo que genera una advertencia:

void PrintMethodNames(Type type)
{
    // ⚠️ IL2070: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods'
    foreach (var method in type.GetMethods())
    {
        Console.WriteLine(method.Name);
    }
}

Paso 1: Identificar qué operación de reflexión se realiza

El código llama a GetMethods(), que requiere que PublicMethods se conserve.

Paso 2: Anotar el parámetro

Agregue DynamicallyAccessedMembers para indicar al cortador lo que hace falta:

void PrintMethodNames(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    // ✅ No warning - trimmer knows to preserve public methods
    foreach (var method in type.GetMethods())
    {
        Console.WriteLine(method.Name);
    }
}

Paso 3: Asegurarse de que los autores de llamadas cumplen el requisito

Al llamar a este método con un tipo conocido (typeof), el requisito se cumple automáticamente:

// ✅ OK - DateTime's public methods will be preserved
PrintMethodNames(typeof(DateTime));

Paso a paso: Propagación de requisitos a través de cadenas de llamadas

Cuando los tipos fluyen a través de varios métodos, se deben propagar los requisitos.

void Method1()
{
    Method2<DateTime>();  // ⚠️ Warning: Generic parameter needs annotation
}

void Method2<T>()
{
    Type t = typeof(T);
    Method3(t);  // ⚠️ Warning: Argument doesn't satisfy requirements
}

void Method3(Type type)
{
    var methods = type.GetMethods();  // ⚠️ Warning: Reflection usage
}

Paso 1: Empezar en el uso de la reflexión

Anota dónde se usa realmente la reflexión:

void Method3(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    var methods = type.GetMethods();  // ✅ Fixed
}

Paso 2: Propagación de la cadena de llamadas

Revise la cadena de llamadas en sentido inverso.

void Method2<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
{
    Type t = typeof(T);
    Method3(t);  // ✅ Fixed - T is annotated
}

Paso 3: Comprobar en el sitio de llamada

void Method1()
{
    Method2<DateTime>();  // ✅ Fixed - DateTime's public methods preserved
}

Para obtener más información sobre cómo fluyen los requisitos a través del código, consulte Descripción del análisis de recorte.

Valores comunes de DynamicallyAccessedMemberTypes

Elija el nivel de acceso mínimo necesario:

Tipo de miembro Cuándo usar
PublicConstructors Usar Activator.CreateInstance() o GetConstructor()
PublicMethods Usar GetMethod() o GetMethods()
PublicFields Utilizar GetField() o GetFields()
PublicProperties El uso de GetProperty() o GetProperties() (serialización)
PublicEvents Usar GetEvent() o GetEvents()

Advertencia

El uso de DynamicallyAccessedMemberTypes.All conserva todos los miembros del tipo objetivo y todos los miembros de sus tipos anidados (pero no de las dependencias transitivas, como los miembros del tipo de retorno de una propiedad). Esto aumenta significativamente el tamaño de la aplicación. Lo más importante es que los miembros conservados sean accesibles, lo que significa que pueden contener su propio código problemático. Por ejemplo, si un miembro conservado llama a un método marcado con RequiresUnreferencedCode, esa advertencia no se puede resolver porque el miembro se conserva a través de una anotación de reflexión en lugar de una llamada explícita. Use los tipos de miembro mínimos necesarios para evitar estos problemas en cascada.

Enfoque 3: Marcar código como incompatible con RequireUnreferencedCode

Cuando el código fundamentalmente no se puede analizar, use RequiresUnreferencedCodeAttribute para documentar la incompatibilidad.

Cuándo usar RequiresUnreferencedCode

Use este atributo cuando:

  • Los tipos se cargan dinámicamente: se usan GetType() con cadenas determinadas por tiempo de ejecución.
  • Los ensamblados se cargan en tiempo de ejecución: mediante LoadFrom(String).
  • Patrones de reflexión complejos: el uso de reflexión es demasiado complejo para anotar.
  • Generación de código en tiempo de ejecución: usando System.Reflection.Emit o el dynamic palabra clave.

Paso a paso: Marcar métodos incompatibles

Paso 1: Identificar código verdaderamente incompatible

Ejemplo de código que no se puede hacer compatible con el recorte:

void LoadPluginByName(string pluginName)
{
    // Type name comes from runtime input - trimmer cannot know what types are needed
    Type pluginType = Type.GetType(pluginName);
    var plugin = Activator.CreateInstance(pluginType);
    // Use plugin...
}

Paso 2: Agregar atributo RequireUnreferencedCode

[RequiresUnreferencedCode("Plugin loading by name is not compatible with trimming. Consider using compile-time plugin registration instead.")]
void LoadPluginByName(string pluginName)
{
    Type pluginType = Type.GetType(pluginName);
    var plugin = Activator.CreateInstance(pluginType);
    // ✅ No warnings inside this method - it's marked as incompatible
}

Paso 3: Los autores de llamadas reciben advertencias

void InitializePlugins()
{
    // ⚠️ IL2026: Using member 'LoadPluginByName' which has 'RequiresUnreferencedCodeAttribute'
    // can break functionality when trimming application code. Plugin loading by name is not
    // compatible with trimming. Consider using compile-time plugin registration instead.
    LoadPluginByName("MyPlugin");
}

Escribir mensajes de advertencia efectivos

Un buen RequiresUnreferencedCode mensaje debe:

  • Indicar qué funcionalidad no es compatible: sea específico de lo que no funciona con el recorte.
  • Sugerir alternativas: guíe a los desarrolladores hacia soluciones compatibles con recortes.
  • Sea conciso: mantenga los mensajes cortos y accionables.
// ❌ Not helpful
[RequiresUnreferencedCode("Uses reflection")]

// ✅ Helpful - explains problem and suggests alternative
[RequiresUnreferencedCode("Dynamic type loading is not compatible with trimming. Use generic type parameters or source generators instead.")]

Para obtener instrucciones más largas, agregue un Url parámetro:

[RequiresUnreferencedCode(
    "Plugin system is not compatible with trimming. See documentation for alternatives.",
    Url = "https://docs.example.com/plugin-trimming")]

Propagación de RequireUnreferencedCode

Cuando un método llama a otro método marcado con RequiresUnreferencedCode, normalmente debe propagar el atributo :

class PluginSystem
{
    // Use a constant for consistent messaging
    const string PluginMessage = "Plugin system is not compatible with trimming. Use compile-time registration instead.";

    [RequiresUnreferencedCode(PluginMessage)]
    private void LoadPluginImplementation(string name)
    {
        // Low-level plugin loading
    }

    [RequiresUnreferencedCode(PluginMessage)]
    public void LoadPlugin(string name)
    {
        LoadPluginImplementation(name);  // ✅ No warning - method is also marked
    }
}

Patrones y soluciones comunes

Patrón: métodos de fábrica con Activator.CreateInstance

// ❌ Before: Produces warning
object CreateInstance(Type type)
{
    return Activator.CreateInstance(type);
}

// ✅ After: Trim-compatible
object CreateInstance(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type)
{
    return Activator.CreateInstance(type);
}

Patrón: Sistemas de extensiones que cargan ensamblajes

// This pattern is fundamentally incompatible with trimming
[RequiresUnreferencedCode("Plugin loading is not compatible with trimming. Consider compile-time plugin registration using source generators.")]
void LoadPluginsFromDirectory(string directory)
{
    foreach (var dll in Directory.GetFiles(directory, "*.dll"))
    {
        Assembly.LoadFrom(dll);
    }
}

Patrón: contenedores de inserción de dependencias

// Complex DI containers are often incompatible
class Container
{
    [RequiresUnreferencedCode("Service resolution uses complex reflection. Consider using source-generated DI or registering services explicitly.")]
    public object Resolve(Type serviceType)
    {
        // Complex reflection to resolve dependencies
    }
}

Enfoque 4: Suprimir advertencias como último recurso

Advertencia

Solo suprima las advertencias de recorte si está absolutamente seguro de que el código es seguro. Las supresiones incorrectas pueden provocar errores en tiempo de ejecución después del recorte.

Use UnconditionalSuppressMessageAttribute cuando haya comprobado que el código es seguro para recortes, pero el optimizador no puede demostrarlo estáticamente.

Cuando la supresión es adecuada

Suprima las advertencias solo cuando:

  1. Ha asegurado manualmente que todo el código necesario se conserve (a través de DynamicDependency u otros mecanismos).
  2. La ruta de acceso del código nunca se ejecuta en escenarios recortados.
  3. Ha probado exhaustivamente la aplicación recortada.

Procedimiento para suprimir advertencias

[RequiresUnreferencedCode("Uses reflection")]
void MethodWithReflection() { /* ... */ }

[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
    Justification = "All referenced types are manually preserved via DynamicDependency attributes")]
void CallerMethod()
{
    MethodWithReflection();  // Warning suppressed
}

Importante

No use SuppressMessage ni #pragma warning disable para advertencias de recorte. Solo funcionan para el compilador, pero no se conservan en el ensamblado compilado. El optimizador funciona en ensamblados compilados y no verá estas supresiones. Use siempre UnconditionalSuppressMessage.

Minimizar el ámbito de supresión

Aplique supresiones al ámbito más pequeño posible. Extraiga la llamada problemática a una función local:

void ProcessData()
{
    InitializeData();

    CallReflectionMethod();  // Only this call is suppressed

    ProcessResults();

    [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
        Justification = "Types are preserved via DynamicDependency on ProcessData method")]
    void CallReflectionMethod()
    {
        MethodWithReflection();
    }
}

Este enfoque:

  • Deja claro qué llamada específica se suprime.
  • Evita la supresión accidental de otras advertencias si cambia el código.
  • Mantiene la justificación cerca de la llamada suprimida.

Sugerencias de solución de problemas

La advertencia persiste después de agregar DynamicallyAccessedMembers

Asegúrese de que haya anotado toda la cadena de llamadas desde el uso de la reflexión hasta el origen de Type:

  1. Buscar dónde se emplea la reflexión (como GetMethods()).
  2. Anotar el parámetro del método.
  3. Siga el Type valor hacia atrás a través de todas las llamadas de método.
  4. Anote cada parámetro, campo o parámetro de tipo genérico de la cadena.

Demasiadas advertencias para abordar

  1. Comience con su propio código: corrija primero las advertencias en el código que controle.
  2. Use TrimmerSingleWarn para ver advertencias individuales de paquetes.
  3. Considere si el recorte es adecuado para su aplicación.
  4. Compruebe las incompatibilidades de recorte conocidas para ver si hay problemas de nivel de marco.

No se tiene claridad sobre qué DynamicallyAccessedMemberTypes utilizar

Examine la API de reflexión a la que se llama:

  • GetMethod() / GetMethods()PublicMethods
  • GetProperty() / GetProperties()PublicProperties
  • GetField() / GetFields()PublicFields
  • GetConstructor() / Activator.CreateInstance()PublicParameterlessConstructor o PublicConstructors
  • GetEvent() / GetEvents()PublicEvents

Use el tipo más estrecho posible para minimizar el tamaño de la aplicación.

Pasos siguientes