Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
In diesem Artikel werden die grundlegenden Konzepte der Trim-Analyse erläutert, um zu verstehen, warum bestimmte Codemuster Warnungen erzeugen und wie Sie Ihren Code trim-kompatibel machen können. Wenn Sie diese Konzepte verstehen, können Sie fundierte Entscheidungen treffen, wenn Sie Trimwarnungen angehen, anstatt einfach Attribute zu verstreuen, um die Warnungen der Tools zu unterdrücken.
Wie der Trimmer Code analysiert
Der Trimmer führt zur Veröffentlichungszeit statische Analysen durch, um zu bestimmen, welcher Code von Ihrer Anwendung verwendet wird. Sie beginnt mit bekannten Einstiegspunkten (z. B. Ihrer Main Methode) und folgt den Codepfaden durch Ihre Anwendung.
Was der Trimmer erkennen kann
Der Trimmer zeichnet sich durch die Analyse direkter, kompilierungszeit-sichtbarer Codemuster aus.
// 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
In diesen Beispielen kann der Trimmer dem Codepfad folgen und DateTime.AddDays, List<string>.Add und MyUtility.Process als verwendeten Code markieren, der in der endgültigen Anwendung beibehalten werden soll.
Was der Trimmer nicht verstehen kann
Der Trimmer hat Schwierigkeiten mit dynamischen Vorgängen, bei denen das Ziel einer Operation erst zur Laufzeit bekannt ist.
// 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?
In diesen Beispielen hat der Trimmer keine Möglichkeit zu wissen:
- Welche Art von Typ der Benutzer eingeben wird
- Welchen Typ
GetSomeObject()zurückgibt? - Welcher Code in der dynamisch geladenen Assembly vorhanden ist
Dies ist das grundlegende Problem, das durch Trim-Warnungen angesprochen wird.
Das Spiegelungsproblem
Reflection ermöglicht Code, Typen und Mitglieder dynamisch zur Laufzeit zu prüfen und aufzurufen. Dies ist leistungsfähig, schafft aber eine Herausforderung für statische Analysen.
Warum Reflektionstäuschungen das Trimmen beeinträchtigen
Betrachten Sie dieses Beispiel:
void PrintMethodNames(Type type)
{
foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}
}
// Called somewhere in the app
PrintMethodNames(typeof(DateTime));
Aus Sicht des Trimmers:
- Es wird festgestellt, dass
type.GetMethods()aufgerufen wird. - Es weiß nicht, was
typesein wird (es ist ein Parameter). - Es kann nicht bestimmt werden, welche Methoden der Typen beibehalten werden müssen.
- Ohne Anleitung kann es Methoden entfernen
DateTime, um den Code zu unterbrechen.
Folglich erzeugt der Trimmer eine Warnung bei diesem Code.
Grundlegendes über "DynamicallyAccessedMembers"
DynamicallyAccessedMembersAttribute löst das Spiegelungsproblem, indem ein expliziter Vertrag zwischen dem Aufrufer und der aufgerufenen Methode erstellt wird.
Der grundlegende Zweck
DynamicallyAccessedMembers teilt dem Trimmer mit: "Dieser Parameter (oder Feld oder Rückgabewert) wird eine Type enthalten, deren bestimmte Mitglieder beibehalten werden müssen, da Reflexion verwendet wird, um darauf zuzugreifen."
Ein konkretes Beispiel
Lassen Sie uns das vorherige Beispiel beheben:
void PrintMethodNames(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}
}
// When this is called...
PrintMethodNames(typeof(DateTime));
Jetzt versteht der Trimmer Folgendes:
-
PrintMethodNameserfordert, dass sein ParameterPublicMethodserhalten bleibt. - Der Aufrufpunkt übergibt
typeof(DateTime). -
DateTimeDaher müssen die öffentlichen Methoden beibehalten werden.
Das Attribut erstellt eine Anforderung , die von der Spiegelungsverwendung in die Quelle des Type Werts rückwärts fließt.
Es ist ein Vertrag, kein Hinweis
Dies ist entscheidend zu verstehen: DynamicallyAccessedMembers ist nicht nur Dokumentation. Der Trimmer setzt diesen Vertrag durch.
Analogie zu generischen Typeinschränkungen
Wenn Sie mit generischen Typeinschränkungen vertraut sind, DynamicallyAccessedMembers funktioniert dies ähnlich. Genauso wie generische Einschränkungen durch Ihren Code fließen:
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 erstellt ähnliche Anforderungen, die über Ihren Code fließen:
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
}
Beide erstellen Verträge, die erfüllt werden müssen, und beide erzeugen Fehler oder Warnungen, wenn der Vertrag nicht erfüllt werden kann.
Wie der Vertrag erzwungen wird
[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());
}
Wenn Sie den Vertrag (wie im zweiten Beispiel) nicht erfüllen können, erhalten Sie eine Warnung.
Grundlegendes zu "RequiresUnreferencedCode"
Einige Codemuster können einfach nicht statisch analyzierbar gemacht werden. Verwenden Sie RequiresUnreferencedCodeAttributefür diese Fälle .
Wann man RequiresUnreferencedCode verwenden sollte
Verwenden Sie das RequiresUnreferencedCodeAttribute Attribut in folgenden Fällen:
- Das Spiegelungsmuster ist grundlegend dynamisch: Laden von Assemblys oder Typen nach Zeichenfolgennamen aus externen Quellen.
- Die Komplexität ist zu hoch zu kommentieren: Code, der Spiegelung auf komplexe, datengesteuerte Weise verwendet.
-
Sie verwenden Laufzeitcodegenerierung: Technologien wie System.Reflection.Emit oder das
dynamicSchlüsselwort.
Beispiel:
[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
}
Der Zweck des Attributs
RequiresUnreferencedCode dient zwei Zwecken:
- Unterdrückt Warnungen innerhalb der Methode: Der Trimmer analysiert oder gibt keine Warnungen bezüglich der Reflexionsnutzung.
- Erstellt Warnungen an Aufrufwebsites: Jeder Code, der diese Methode aufruft, ruft eine Warnung ab.
Diese Warnung leitet weiter, um Entwicklern Einblicke in trim-inkompatible Codepfade zu geben.
Schreiben guter Nachrichten
Die Meldung sollte Entwicklern helfen, ihre Optionen zu verstehen:
// ❌ 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")]
Wie Anforderungen durch den Code fließen
Wenn Sie wissen, wie Anforderungen verteilt werden, wissen Sie, wo Attribute hinzugefügt werden sollen.
Anforderungen fließen rückwärts
Anforderungen fließen von dem Punkt, an dem die Reflexion verwendet wird, zurück zu dem Ursprung der Type.
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
}
Damit diese Trim-Kompatibilität gegeben ist, müssen Sie die Kette annotieren:
void ProcessData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
{
var type = typeof(T);
DisplayInfo(type);
}
void DisplayInfo(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
type.GetMethods();
}
Jetzt fließen die Anforderungen: GetMethods() erfordert PublicMethods → type Parameter benötigt PublicMethods → generischer T benötigt PublicMethods → DateTime benötigt PublicMethods beibehalten werden.
Anforderungen fließen durch Speicher
Anforderungen fließen auch durch Felder und Eigenschaften:
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
}
}
Auswählen des richtigen Ansatzes
Wenn Sie auf Code stoßen, der Spiegelung benötigt, folgen Sie dieser Entscheidungsstruktur:
1. Können Sie Reflexion vermeiden?
Die beste Lösung besteht darin, nach Möglichkeit Reflexionen zu vermeiden:
// ❌ 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. Ist der Typ zur Kompilierungszeit bekannt?
Wenn die Reflexion erforderlich ist, aber die Typen bekannt sind, verwenden Sie DynamicallyAccessedMembersFolgendes:
// ✅ Trim-compatible
void Serialize<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(T obj)
{
foreach (var prop in typeof(T).GetProperties())
{
// Serialize property
}
}
3. Ist das Muster grundlegend dynamisch?
Wenn die Typen wirklich erst zur Laufzeit bekannt sind, verwenden Sie 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
}
Allgemeine Muster und Lösungen
Entwurfsmuster: Fabrikmethoden
// 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);
}
Muster: Plug-In-Systeme
// 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
Wichtige Erkenntnisse
- Der Trimmer verwendet statische Analysen – es kann nur Codepfade verstehen, die zur Kompilierungszeit sichtbar sind.
- Reflection bricht die statische Analyse – der Trimmer kann nicht erkennen, was die Reflection zur Laufzeit zugreifen wird.
- DynamicallyAccessedMembers erstellt Verträge – sie teilt dem Trimmer mit, was beibehalten werden muss.
-
Anforderungen fließen rückwärts – von der Spiegelungsverwendung zurück zur Quelle des
TypeWerts. - RequiresUnreferencedCode dokumentiert Inkompatibilitäten - verwenden Sie es, wenn der Code nicht analysierbar gemacht werden kann.
- Attribute sind nicht nur Hinweise – der Trimmer erzwingt Verträge und erzeugt Warnungen, wenn sie nicht erfüllt werden können.
Nächste Schritte
- Beheben von Kürzungswarnungen – Wenden Sie diese Konzepte an, um Warnungen in Ihrem Code zu beheben.
- Bibliotheken für das Trimmen vorbereiten – Machen Sie Ihre Bibliotheken trim-kompatibel
- Warnungsreferenz kürzen – Detaillierte Informationen zu bestimmten Warnungen