Freigeben über


Beheben von Kürzungswarnungen

Wenn Sie das Kürzen in Ihrer Anwendung aktivieren, führt das .NET SDK statische Analysen aus, um Codemuster zu erkennen, die möglicherweise nicht mit dem Kürzen kompatibel sind. Kürzungswarnungen deuten auf potenzielle Probleme hin, die nach dem Kürzen zu Verhaltensänderungen oder Abstürze führen können.

Eine App, die Kürzungen verwendet, sollte keine Kürzungswarnungen erzeugen. Wenn Warnungen zu Kürzen vorhanden sind, testen Sie die App nach dem Kürzen gründlich, um sicherzustellen, dass keine Verhaltensänderungen vorgenommen werden.

Dieser Artikel enthält praktische Workflows zum Behandeln von Kürzungswarnungen. Weitere Informationen dazu, warum diese Warnungen auftreten und wie das Kürzen funktioniert, finden Sie unter "Grundlegendes zur Kürzungsanalyse".

Grundlegendes zu Warnungskategorien

Trim-Warnungen fallen in zwei Hauptkategorien:

  • Code inkompatibel mit Kürzung - Gekennzeichnet mit RequiresUnreferencedCodeAttribute. Der Code ist grundsätzlich nicht analysierbar (z. B. dynamisches Assemblyladen und komplexe Reflexionsmuster). Die Methode ist als inkompatibel gekennzeichnet, und Aufrufer erhalten Warnungen.

  • Code mit Anforderungen – Mit Anmerkungen DynamicallyAccessedMembersAttributeversehen. Spiegelung wird verwendet, aber Typen werden zur Kompilierungszeit bekannt. Wenn die Anforderungen erfüllt sind, wird der Code vollständig trim-kompatibel.

Workflow: Ermitteln des richtigen Ansatzes

Wenn eine Kürzungswarnung auftritt, führen Sie die folgenden Schritte in der folgenden Reihenfolge aus:

  1. Spiegelung beseitigen - Dies ist immer die beste Option, wenn möglich.
  2. Verwenden Sie DynamicallyAccessedMembers - Wenn Typen bekannt sind, stellen Sie die Trimm-Kompatibilität des Codes sicher.
  3. Use RequiresUnreferencedCode – Wenn tatsächlich dynamisch, dokumentieren Sie die Inkompatibilität.
  4. Unterdrücken von Warnungen als letzte Möglichkeit – Nur wenn Sie sicher sind, dass der Code sicher ist.

Ansatz 1: Spiegelung beseitigen

Die beste Lösung besteht darin, die Reflexion möglichst vollständig zu vermeiden. Dadurch wird Ihr Code schneller und vollständig trim-kompatibel.

Verwendung von Kompilierungszeitgenerika

Ersetzen Sie Laufzeittypvorgänge durch generische Kompilierungszeitparameter:

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

Verwenden von Quellgeneratoren

Moderne .NET stellt Quellgeneratoren für allgemeine Spiegelungsszenarien bereit:

Weitere Informationen finden Sie unter "Bekannte Kürzungsinkompatibilitäten".

Ansatz 2: Code trim-kompatibel machen mit DynamicallyAccessedMembers

Wenn eine Spiegelung erforderlich ist, aber die Typen zur Kompilierungszeit bekannt sind, verwenden Sie DynamicallyAccessedMembersAttribute, um Ihren Code trim-kompatibel zu machen.

Schritt für Schritt: Kommentieren der Spiegelungsverwendung

Betrachten Sie dieses Beispiel, das eine Warnung erzeugt:

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

Schritt 1: Ermitteln, welche Spiegelungsoperation ausgeführt wird

Der Code ruft GetMethods() auf, was erfordert, dass PublicMethods beibehalten wird.

Schritt 2: Kommentieren des Parameters

Fügen Sie DynamicallyAccessedMembers hinzu, um dem Trimmer mitzuteilen, was erforderlich ist:

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);
    }
}

Schritt 3: Sicherstellen, dass Anrufer die Anforderung erfüllen

Beim Aufrufen dieser Methode mit einem bekannten Typ (typeof) wird die Anforderung automatisch erfüllt:

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

Schritt für Schritt: Verteilen von Anforderungen über Anrufketten

Wenn Typen mehrere Methoden durchlaufen, müssen Sie Anforderungen weitergeben:

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
}

Schritt 1: Beginnen Sie bei der Spiegelungsverwendung

Anmerkungen dazu, wo die Spiegelung tatsächlich verwendet wird:

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

Schritt 2: Verteilen der Anrufkette

Arbeiten Sie rückwärts durch die Anrufkette:

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

Schritt 3: Überprüfen an der Anrufwebsite

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

Weitere Informationen dazu, wie Anforderungen über Code fließen, finden Sie unter Grundlegendes zur Kürzungsanalyse.

Häufige DynamicallyAccessedMemberTypes-Werte

Wählen Sie die erforderliche Mindestzugriffsstufe aus:

Membertyp Wann verwenden
PublicConstructors Verwenden Sie Activator.CreateInstance() oder GetConstructor()
PublicMethods Verwenden GetMethod() oder GetMethods()
PublicFields Verwenden Sie GetField() oder GetFields()
PublicProperties Verwenden GetProperty() oder GetProperties() (Serialisierung)
PublicEvents Verwenden Sie GetEvent() oder GetEvents()

Warnung

Die Verwendung von DynamicallyAccessedMemberTypes.All behält alle Mitglieder des Zieltyps und alle Mitglieder in den geschachtelten Typen bei, jedoch keine transitiven Abhängigkeiten wie Mitglieder des Rückgabetyps einer Eigenschaft. Dadurch wird die App-Größe erheblich erhöht. Wichtiger ist, dass beibehaltene Mitglieder erreichbar werden, was bedeutet, dass sie eigenen problematischen Code enthalten können. Wenn beispielsweise ein beibehaltenes Element eine Methode aufruft, die mit RequiresUnreferencedCode markiert ist, kann diese Warnung nicht aufgelöst werden, da das Element durch Reflexionsanmerkung anstatt eines expliziten Aufrufs gehalten wird. Verwenden Sie die minimal erforderlichen Membertypen, um diese kaskadierenden Probleme zu vermeiden.

Ansatz 3: Markieren von Code als nicht kompatibel mit RequiresUnreferencedCode

Wenn Code grundsätzlich nicht analysierbar gemacht werden kann, verwenden Sie RequiresUnreferencedCodeAttribute, um die Inkompatibilität zu dokumentieren.

Wann man RequiresUnreferencedCode verwenden sollte

Verwenden Sie dieses Attribut in folgenden Fällen:

  • Typen werden dynamisch geladen: Verwenden GetType() mit laufzeitbestimmten Zeichenfolgen.
  • Assemblies werden zur Laufzeit geladen: LoadFrom(String) verwenden.
  • Komplexe Spiegelungsmuster: Spiegelungsverwendung zu komplex, um Anmerkungen zu machen.
  • Laufzeitcodegenerierung: Verwenden System.Reflection.Emit oder das dynamic Schlüsselwort.

Schritt für Schritt: Kennzeichnen inkompatibler Methoden

Schritt 1: Identifizieren von wirklich inkompatiblem Code

Beispiel für Code, der nicht trim-kompatibel gemacht werden kann:

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

Schritt 2: Fügen Sie das RequiresUnreferencedCode-Attribut hinzu

[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
}

Schritt 3: Anrufer erhalten Warnungen

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");
}

Schreiben effektiver Warnmeldungen

Eine gute RequiresUnreferencedCode Nachricht sollte:

  • Geben Sie an, welche Funktionalität nicht kompatibel ist: Geben Sie an, was nicht mit dem Kürzen funktioniert.
  • Alternativen vorschlagen: Führen Sie Entwickler zu trimkompatiblen Lösungen.
  • Seien Sie präzise: Halten Sie Nachrichten kurz und umsetzbar.
// ❌ 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.")]

Fügen Sie für längere Anleitungen einen Url Parameter hinzu:

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

Ausbreitung von RequiresUnreferencedCode

Wenn eine Methode eine andere methode aufruft, die mit RequiresUnreferencedCodemarkiert ist, müssen Sie das Attribut in der Regel weitergeben:

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

Allgemeine Muster und Lösungen

Entwurfsmuster: Factory-Methoden mit 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);
}

Muster: Plug-In-Systeme laden Assemblys

// 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);
    }
}

Muster: Container für Abhängigkeitseinfügung

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

Ansatz 4: Unterdrücken von Warnungen als letzte Möglichkeit

Warnung

Unterdrücken Sie Kürzungswarnungen nur, wenn Sie absolut sicher sind, dass der Code sicher ist. Falsche Unterdrückungen können nach dem Kürzen zu Laufzeitfehlern führen.

Verwenden Sie UnconditionalSuppressMessageAttribute, wenn Sie überprüft haben, dass der Code zuschneidend sicher ist, aber der Trimmer ihn nicht statisch nachweisen kann.

Wenn die Unterdrückung geeignet ist

Warnungen nur unterdrücken, wenn:

  1. Sie haben manuell sichergestellt, dass der gesamte erforderliche Code beibehalten wird (über DynamicDependency oder andere Mechanismen).
  2. Der Codepfad wird nie in gekürzten Szenarien ausgeführt.
  3. Sie haben die zugeschnittene Anwendung gründlich getestet.

Wie man Warnungen unterdrückt

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

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

Wichtig

Verwenden Sie SuppressMessage oder #pragma warning disable nicht für Trim-Warnungen. Diese funktionieren nur für den Compiler, werden aber nicht in der kompilierten Assembly beibehalten. Der Trimmer arbeitet auf kompilierten Assemblys und sieht diese Unterdrückungen nicht. Verwenden Sie immer UnconditionalSuppressMessage.

Minimieren des Unterdrückungsbereichs

Wenden Sie Unterdrückungen auf den kleinsten möglichen Bereich an. Extrahieren Sie den problematischen Aufruf in eine lokale Funktion:

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();
    }
}

Dieser Ansatz:

  • Stellt klar, welcher spezifische Aufruf unterdrückt wird.
  • Verhindert versehentlich das Unterdrücken anderer Warnungen, wenn sich der Code ändert.
  • Behält die Begründung nahe beim unterdrückten Aufruf bei.

Tipps zur Problembehandlung

Die Warnung bleibt auch nach dem Hinzufügen von "DynamicallyAccessedMembers" bestehen.

Stellen Sie sicher, dass Sie die gesamte Aufrufkette von der Reflection-Nutzung bis zurück zur Quelle des Type annotiert haben.

  1. Finden Sie heraus, wo Reflection verwendet wird (z. B. GetMethods()).
  2. Kommentieren Sie den Parameter dieser Methode.
  3. Folgen Sie dem Type Wert rückwärts durch alle Methodenaufrufe.
  4. Kommentieren Sie jeden Parameter, Feld oder generischen Typparameter in der Kette.

Zu viele Warnungen, die adressiert werden sollen

  1. Beginnen Sie mit Ihrem eigenen Code – Beheben Sie Warnungen im Code, den Sie zuerst steuern.
  2. Verwenden Sie TrimmerSingleWarn, um einzelne Warnungen aus Paketen anzuzeigen.
  3. Überlegen Sie, ob das Kürzen für Ihre Anwendung geeignet ist.
  4. Überprüfen Sie bekannte Kürzungsinkompatibilitäten für Probleme auf Frameworkebene.

Nicht sicher, welche DynamicallyAccessedMemberTypes verwendet werden sollen

Sehen Sie sich die Spiegelungs-API an, die aufgerufen wird:

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

Verwenden Sie den schmalsten Typ, der möglich ist, um die App-Größe zu minimieren.

Nächste Schritte