Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Quando si abilita il taglio nell'applicazione, .NET SDK esegue l'analisi statica per rilevare i modelli di codice che potrebbero non essere compatibili con il taglio. Gli avvisi di taglio indicano potenziali problemi che potrebbero causare modifiche al comportamento o arresti anomali dopo il taglio.
Un'app che usa il taglio non dovrebbe generare avvisi di taglio. Se sono presenti avvisi di riduzione, testare accuratamente l'app dopo la riduzione per accertarsi che non vi siano cambiamenti nel comportamento.
Questo articolo fornisce flussi di lavoro pratici per risolvere gli avvisi di taglio. Per una comprensione più approfondita dei motivi per cui si verificano questi avvisi e del funzionamento del taglio, vedere Informazioni sull'analisi del taglio.
Informazioni sulle categorie di avviso
Gli avvisi di taglio rientrano in due categorie principali:
Codice incompatibile con il taglio - Contrassegnato con RequiresUnreferencedCodeAttribute. Fondamentalmente, il codice non può essere reso analizzabile (ad esempio, caricamento dinamico di assembly o schemi di riflessione complessi). Il metodo è contrassegnato come incompatibile e i chiamanti ricevono avvisi.
Codice con i requisiti : annotato con DynamicallyAccessedMembersAttribute. Viene usata la Reflection, ma i tipi sono noti in fase di compilazione. Quando vengono soddisfatti i requisiti, il codice diventa completamente compatibile con il trim.
Flusso di lavoro: determinare l'approccio corretto
Quando viene visualizzato un avviso di taglio, seguire questa procedura nell'ordine seguente:
- Elimina riflessione - questa è sempre l'opzione migliore, se possibile.
- Usare DynamicallyAccessedMembers - Se i tipi sono noti, rendere il codice compatibile con il trimming.
- Usare RequiresUnreferencedCode : se è veramente dinamico, documentare l'incompatibilità.
- Eliminare gli avvisi come ultima risorsa : solo se si è certi che il codice sia sicuro.
Approccio 1: Eliminare la riflessione
La soluzione migliore consiste nell'evitare completamente la riflessione quando possibile. In questo modo il tuo codice diventa più veloce e completamente compatibile con le operazioni di ottimizzazione.
Utilizzare generics in fase di compilazione
Sostituire le operazioni del tipo di runtime con parametri generici in fase di compilazione:
// ❌ 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...
}
Usare generatori di origine
.NET moderno offre generatori di codice sorgente per scenari comuni di reflection:
- Serializzazione: usare System.Text.Json per la generazione del codice sorgente anziché i serializzatori reflection-based
- Configurazione: usare il generatore di origini vincolanti di configurazione
Per altre informazioni, vedere Incompatibilità di riduzione note.
Approccio 2: Rendere il codice compatibile con DynamicallyAccessedMembers
Quando i tipi sono noti in fase di compilazione ma è necessaria la reflection, usa DynamicallyAccessedMembersAttribute per rendere il codice compatibile con il trimming.
Procedura dettagliata: Annotare l'utilizzo della riflessione
Si consideri questo esempio che genera un avviso:
void PrintMethodNames(Type type)
{
// ⚠️ IL2070: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods'
foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}
}
Passaggio 1: Identificare l'operazione di riflessione eseguita
Il codice chiama GetMethods(), che deve PublicMethods essere mantenuto.
Passaggio 2: Annotare il parametro
Aggiungere DynamicallyAccessedMembers per indicare al trimmer cosa è necessario:
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);
}
}
Passaggio 3: Assicurarsi che i chiamanti soddisfino il requisito
Quando si chiama questo metodo con un tipo noto (typeof), il requisito viene soddisfatto automaticamente:
// ✅ OK - DateTime's public methods will be preserved
PrintMethodNames(typeof(DateTime));
Procedura dettagliata: Propagare i requisiti tramite catene di chiamate
Quando i tipi passano attraverso più metodi, è necessario propagare i requisiti:
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
}
Passaggio 1: Iniziare a utilizzare la reflection
Annotare dove viene effettivamente usata la reflection:
void Method3(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
var methods = type.GetMethods(); // ✅ Fixed
}
Passaggio 2: Propagare verso l'alto la catena di chiamate
Ripercorri la catena di chiamate al contrario.
void Method2<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
{
Type t = typeof(T);
Method3(t); // ✅ Fixed - T is annotated
}
Passaggio 3: Verificare nel sito di chiamata
void Method1()
{
Method2<DateTime>(); // ✅ Fixed - DateTime's public methods preserved
}
Per altre informazioni sul flusso dei requisiti attraverso il codice, vedere Informazioni sull'analisi dei tagli.
Valori comuni di DynamicallyAccessedMemberTypes
Scegliere il livello di accesso minimo necessario:
| Tipo di membro | Quando utilizzare |
|---|---|
PublicConstructors |
Uso di Activator.CreateInstance() o GetConstructor() |
PublicMethods |
Uso di GetMethod() o GetMethods() |
PublicFields |
Uso di GetField() o GetFields() |
PublicProperties |
Uso di GetProperty() o GetProperties() (serializzazione) |
PublicEvents |
Uso di GetEvent() o GetEvents() |
Avviso
L'uso di DynamicallyAccessedMemberTypes.All conserva tutti i membri nel tipo di destinazione e tutti i membri nei relativi tipi annidati (ma non le dipendenze transitive come i membri del tipo di ritorno di una proprietà). Questo aumenta significativamente le dimensioni dell'app. Più importante, i membri mantenuti diventano raggiungibili, il che significa che possono contenere il proprio codice problematico. Ad esempio, se un membro preservato chiama un metodo contrassegnato con RequiresUnreferencedCode, tale avviso non può essere risolto perché il membro viene mantenuto tramite l'annotazione di reflection anziché una chiamata esplicita. Usare i tipi di membri minimi necessari per evitare questi problemi di propagazione.
Approccio 3: Contrassegnare il codice come incompatibile con RequiresUnreferencedCode
Quando il codice fondamentalmente non può essere reso analizzabile, usare RequiresUnreferencedCodeAttribute per documentare l'incompatibilità.
Quando usare RequiresUnreferencedCode
Usare questo attributo quando:
- I tipi vengono caricati in modo dinamico: utilizzando GetType() con stringhe determinate a runtime.
- Gli assembly vengono caricati in fase di esecuzione: usando LoadFrom(String).
- Modelli di riflessione complessi: l'uso della riflessione troppo complesso da annotare.
-
Generazione del codice di runtime: utilizzare System.Reflection.Emit oppure la parola chiave
dynamic.
Procedura dettagliata: Contrassegnare i metodi incompatibili
Passaggio 1: Identificare il codice veramente incompatibile
Esempio di codice che non può essere reso compatibile con trim:
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...
}
Passaggio 2: Aggiungere l'attributo RequiresUnreferencedCode
[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
}
Passaggio 3: I chiamanti ricevono avvisi
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");
}
Scrittura di messaggi di avviso effettivi
Un buon RequiresUnreferencedCode messaggio dovrebbe:
- Specificare le funzionalità non compatibili: sii specifico su ciò che non funziona con il ritaglio.
- Suggerisci alternative: guida gli sviluppatori verso soluzioni compatibili con trim.
- Concisa: mantenere brevi e interattivi i messaggi.
// ❌ 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.")]
Per indicazioni più lunghe, aggiungere un Url parametro:
[RequiresUnreferencedCode(
"Plugin system is not compatible with trimming. See documentation for alternatives.",
Url = "https://docs.example.com/plugin-trimming")]
Propagazione di RequiresUnreferencedCode
Quando un metodo chiama un altro metodo contrassegnato con RequiresUnreferencedCode, in genere è necessario propagare l'attributo :
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
}
}
Modelli e soluzioni comuni
Modello: metodi di fabbrica 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);
}
Modello: Sistemi plugin che caricano assembly
// 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);
}
}
Modello: contenitori di iniezione delle dipendenze
// 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
}
}
Approccio 4: Eliminare gli avvisi come ultima risorsa
Avviso
Eliminare gli avvisi di taglio solo se si è assolutamente certi che il codice sia sicuro. Le soppressioni errate possono causare errori di runtime dopo la riduzione.
Usare UnconditionalSuppressMessageAttribute quando si è verificato che il codice è trim-safe, ma il trimmer non può dimostrarlo staticamente.
Quando l'eliminazione è appropriata
Eliminare gli avvisi solo quando:
- Hai assicurato manualmente che tutto il codice richiesto sia preservato (tramite
DynamicDependencyo altri meccanismi). - Il percorso del codice non viene mai eseguito in scenari di riduzione.
- L'applicazione ottimizzata è stata testata accuratamente.
Come eliminare gli avvisi
[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
Non usare SuppressMessage o #pragma warning disable per gli avvisi di taglio. Queste operazioni funzionano solo per il compilatore, ma non vengono mantenute nell'assembly compilato. Il trimmer opera su assembly compilati e non visualizzerà queste eliminazioni. Usare sempre UnconditionalSuppressMessage.
Ridurre al minimo l'ambito di soppressione
Applicare eliminazioni al più piccolo ambito possibile. Estrarre la chiamata problematica in una funzione locale:
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();
}
}
Questo approccio:
- Rende chiaro quale chiamata specifica viene eliminata.
- Impedisce l'eliminazione accidentale di altri avvisi se il codice cambia.
- Mantiene la giustificazione vicina alla chiamata soppressa.
Suggerimenti per la risoluzione dei problemi
L'avviso persiste dopo l'aggiunta di DynamicallyAccessedMembers
Assicurarsi di aver annotato l'intera catena di chiamate dall'utilizzo della Reflection all'origine di Type:
- Trovare dove viene usata la reflection (ad esempio
GetMethods()). - Annotare il parametro del metodo.
- Seguire il valore all'indietro in tutte le chiamate al
Typemetodo. - Annotare ogni parametro, campo o parametro di tipo generico nella catena.
Troppi avvisi da risolvere
- Inizia con il tuo codice: correggi prima gli avvisi nel codice che controlli.
- Usare
TrimmerSingleWarnper visualizzare singoli avvisi dai pacchetti. - Valutare se il taglio è appropriato per l'applicazione.
- Controllare le incompatibilità note di trimmatura per i problemi a livello di framework.
Non si è certi di quale oggetto DynamicallyAccessedMemberTypes usare
Esaminare l'API di riflessione chiamata:
-
GetMethod()/GetMethods()→PublicMethods -
GetProperty()/GetProperties()→PublicProperties -
GetField()/GetFields()→PublicFields -
GetConstructor()/Activator.CreateInstance()→PublicParameterlessConstructoroPublicConstructors -
GetEvent()/GetEvents()→PublicEvents
Usare il tipo più stretto possibile per ridurre al minimo le dimensioni dell'app.
Passaggi successivi
- Comprendere l'analisi del taglio - Scopri i concetti fondamentali dietro gli avvisi sul taglio
- Preparare le librerie per la riduzione - Rendere le librerie compatibili con la riduzione
- Riferimento agli avvisi di taglio - Informazioni dettagliate sui codici di avviso specifici
- Incompatibilità note - Modelli che non possono essere resi compatibili con il taglio