Trimming e limitazioni della reflection
Gestisca gli avvisi del trimming, usi [DynamicallyAccessedMembers] e sostituisca la reflection a runtime con alternative generate dal codice sorgente.
Trimming e limitazioni della reflection è una lezione C# Academy gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento C# Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso C# Academy include 4 lezioni in totale.
Che cos'è il trimming dell'IL?
Il trimming dell'IL è un'ottimizzazione eseguita in fase di pubblicazione che rimuove dall'output i tipi, i metodi e gli assembly inutilizzati. Il risultato è un file eseguibile più piccolo. Viene abilitato automaticamente per Native AOT e per le pubblicazioni autonome in un singolo file.
Abilitazione del trimming
Abiliti il trimming nel file di progetto. Può controllare la modalità di trimming da partial (rimuove solo gli assembly inutilizzati) a full (rimuove i membri inutilizzati all'interno degli assembly).
// .csproj
<PropertyGroup>
<!-- Enable trimming on publish -->
<PublishTrimmed>true</PublishTrimmed>
<!-- full: aggressive — trims unused members within assemblies
partial: conservative — only removes unused assemblies -->
<TrimMode>full</TrimMode>
<!-- Treat all trimmer warnings as errors (recommended for CI) -->
<TrimmerRootAssemblies>MyApp</TrimmerRootAssemblies>
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
</PropertyGroup>
// Publish:
dotnet publish -c Release -r linux-x64 --self-containedCome funziona il trimmer
Il trimmer esegue un'analisi della raggiungibilità a partire dai punti di ingresso (Main, servizi registrati, attributi). Tutto il codice non raggiungibile da queste radici viene rimosso.
// Example: only MyApp.Program.Main is a root
// Code flow:
// Main → WebApplication.Run → MapGet → MyHandler
// Everything else is trimmed
// What gets KEPT:
class MyHandler { public string Handle() => "ok"; }
// What gets TRIMMED (if not reached):
class UnusedService { public void DoWork() { } }
// Problem: reflection can access UnusedService at runtime
// But the trimmer can't know that at compile time
// → UnusedService is trimmed → MissingMethodException at runtimeReflection e trimming
La reflection è la principale fonte di incompatibilità con il trimming. Il trimmer non può analizzare staticamente quali tipi verranno usati tramite Type.GetType() o Activator.CreateInstance().
// PROBLEMATIC: type name comes from config at runtime
var typeName = config["Plugin:Type"]!;
var type = Type.GetType(typeName); // type may have been trimmed!
var instance = Activator.CreateInstance(type!); // MissingMethodException
// PROBLEMATIC: LINQ expressions with reflection
var query = dbContext.Set<T>() // T discovered via reflection
.Where(BuildExpression<T>("Name", "Alice"));
// PROBLEMATIC: attribute scanning
var handlers = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(t => t.HasCustomAttribute<HandlerAttribute>());
// All handler types may be trimmed awayAttributo DynamicDependency
[DynamicDependency] indica al trimmer di conservare membri specifici che si sa verranno usati tramite reflection.
// Keep all public constructors of MyPlugin:
[DynamicDependency(
DynamicallyAccessedMemberTypes.PublicConstructors,
typeof(MyPlugin))]
public static IPlugin CreatePlugin()
=> (IPlugin)Activator.CreateInstance(typeof(MyPlugin))!;
// Keep a specific method by name:
[DynamicDependency("ProcessOrder", typeof(OrderHandler))]
public static void Bootstrap() { }
// Keep everything on a type (use sparingly):
[DynamicDependency(
DynamicallyAccessedMemberTypes.All,
typeof(LegacyReflectionHelper))]
public static void EnsurePreserved() { }Annotazione DynamicallyAccessedMembers
[DynamicallyAccessedMembers] annota parametri, campi e proprietà per indicare al trimmer quali membri di un Type verranno usati tramite reflection in fase di esecuzione.
// Annotate a method parameter:
public void Register(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
Type serviceType)
{
// trimmer knows to preserve public constructors of whatever Type is passed
var instance = Activator.CreateInstance(serviceType)!;
}
// Annotate a generic parameter:
public static T Create<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
T>() where T : new()
=> new T();
// Annotate a field:
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
private Type _modelType = typeof(OrderModel);RequiresUnreferencedCode
Contrassegni con [RequiresUnreferencedCode] i metodi che dipendono fondamentalmente dalla reflection. In questo modo vengono segnalati avvisi ai chiamanti, che sanno quindi che il codice potrebbe non funzionare dopo il trimming.
// Mark a method that uses reflection internally:
[RequiresUnreferencedCode("Uses Type.GetType() — not safe for trimming")]
public static object LoadPlugin(string typeName)
{
var type = Type.GetType(typeName)!
?? throw new InvalidOperationException($"Type {typeName} not found");
return Activator.CreateInstance(type)!;
}
// Callers will see a trimmer warning:
// IL2026: Members decorated with [RequiresUnreferencedCode]
// need their callers to be decorated with the same attribute
// or be suppressed with [UnconditionalSuppressMessage]
// The warning reminds callers to use a trim-safe alternative
// or add a DynamicDependency for the types they expectAlternative compatibili con il trimming
In .NET moderno, molti schemi che fanno ampio uso della reflection hanno alternative compatibili con il trimming. Preferisca queste alternative per evitare problemi di trimming.
// INSTEAD OF: Type.GetType(name) + Activator.CreateInstance
// USE: registered factories or switch expressions:
public IHandler CreateHandler(string name) => name switch
{
"order" => new OrderHandler(),
"email" => new EmailHandler(),
_ => throw new ArgumentException($"Unknown: {name}")
};
// INSTEAD OF: reflection-based JSON serialization
// USE: source-generated JsonSerializerContext
// INSTEAD OF: attribute scanning via Assembly.GetTypes()
// USE: compile-time source generators
// INSTEAD OF: dynamic proxy libraries (Castle, DispatchProxy)
// USE: source-generated interceptors (.NET 8+)Descrittore XML del trimming
Usi un descrittore XML radice per conservare interi namespace o assembly che il trimmer non può analizzare, ad esempio le librerie di terze parti che fanno ampio uso della reflection.
<!-- TrimmerRoots.xml -->
<linker>
<!-- Keep everything in LegacyLib -->
<assembly fullname="LegacyLib">
<type fullname="LegacyLib.*" preserve="all" />
</assembly>
<!-- Keep a specific type -->
<assembly fullname="MyApp">
<type fullname="MyApp.Plugins.PluginLoader" preserve="all" />
</assembly>
</linker>
// Reference in .csproj:
<ItemGroup>
<TrimmerRootDescriptor Include="TrimmerRoots.xml" />
</ItemGroup>Caso reale: risoluzione degli avvisi del trimming
Un flusso di lavoro pratico per risolvere gli avvisi del trimmer in una codebase reale.
// Step 1: enable analyzer during development
// .csproj:
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
// Step 2: build and read warnings:
// warning IL2026: 'MyMapper.MapDynamic(Type)' uses
// [RequiresUnreferencedCode] via 'Type.GetMethod'
// Step 3: fix the root cause:
// BEFORE:
public void MapDynamic(Type t)
{
var method = t.GetMethod("MapFrom")!; // IL2026
method.Invoke(null, null);
}
// AFTER: use an interface instead of reflection
public void Map(IMapper mapper) => mapper.MapFrom();
// Step 4: if fix is not feasible, add annotation and document:
[RequiresUnreferencedCode("Uses reflection: ensure target types are preserved")]
public void MapDynamic(Type t) { /* ... */ }Verifica rapida
Che cosa indica l'attributo [DynamicallyAccessedMembers] al trimmer dell'IL?
Riepilogo: limitazioni del trimming e della reflection
Punti chiave:
- Il trimming rimuove il codice non raggiungibile in fase di pubblicazione — la reflection compromette questa analisi
- Lo abiliti con
<PublishTrimmed>true</PublishTrimmed>; usiTrimMode=fullper ottenere il massimo risparmio [DynamicDependency]: conserva esplicitamente membri specifici per la reflection[DynamicallyAccessedMembers]: annota i parametriTypeaffinché il trimmer sappia che cosa conservare[RequiresUnreferencedCode]: avvisa i chiamanti che un metodo non è compatibile con il trimming- Preferisca i generatori di codice sorgente e le interfacce agli schemi che fanno ampio uso della reflection
Domande Frequenti
La lezione «Trimming e limitazioni della reflection» è gratuita?
Sì — il testo completo di «Trimming e limitazioni della reflection» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso C# Academy, passa a CoddyKit PRO. Il corso C# Academy include 4 lezioni in totale.
Cosa imparerò in «Trimming e limitazioni della reflection»?
Gestisca gli avvisi del trimming, usi [DynamicallyAccessedMembers] e sostituisca la reflection a runtime con alternative generate dal codice sorgente. Eserciti C# Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare C# Academy?
Non è richiesta alcuna esperienza precedente. C# Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.
Quanto tempo richiede la lezione «Trimming e limitazioni della reflection»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione C# Academy?
Sì. Ogni lezione C# Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Compilazione Native AOT
- Trimming e limitazioni della reflection
- ReadyToRun e compilazione a livelli
- Benchmark con BenchmarkDotNet