Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Tento článek vysvětluje základní koncepty za analýzou trimování, které vám pomohou pochopit, proč určité vzory kódu generují varování, a jak zajistit, aby váš kód byl kompatibilní s procesem trimování. Porozumění těmto konceptům vám pomůže učinit informovaná rozhodnutí při řešení upozornění na zmenšení, nikoli jen „šířením atributů k potlačení varování nástroje“.
Jak ořezávač analyzuje kód
Zatřihovací modul provádí statickou analýzu v době publikování a zjišťuje, který kód používá vaše aplikace. Začíná ze známých vstupních bodů (jako je vaše Main metoda) a řídí se cestami kódu v aplikaci.
Co může zatřižovač pochopit
Ořezávač vyniká při analýze přímých vzorů kódu viditelných při kompilaci:
// 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
V těchto příkladech může trimmer následovat cestu kódu a označit DateTime.AddDays, List<string>.Add a MyUtility.Process jako použitý kód, který by měl být zachován v konečné aplikaci.
Co trimmer nedokáže pochopit
Zastřihovač má problémy s dynamickými operacemi, kde cíl operace není známý, dokud není spuštěný runtime.
// 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?
V těchto příkladech řezačka nemá žádný způsob, jak to vědět.
- Který typ uživatel zadá
- Jaký typ
GetSomeObject()vrátí - Jaký kód existuje v dynamicky načteném sestavení
Toto je základní problém, který řeší upozornění.
Problém reflexe
Reflexe kódu umožňuje zkoumat a volat typy a členy dynamicky během běhu. To je výkonné, ale vytváří výzvu pro statickou analýzu.
Proč reflexe přeruší oříznutí
Podívejte se na tento příklad:
void PrintMethodNames(Type type)
{
foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}
}
// Called somewhere in the app
PrintMethodNames(typeof(DateTime));
Z pohledu trimmeru:
- Je vidět, že
type.GetMethods()je zavolán. - Neví, co
typebude (jedná se o parametr). - Nemůže určit, které metody typů je potřeba zachovat.
- Bez pokynů může dojít k odebrání metod z
DateTime, což přeruší kód.
V důsledku toho trimmer vygeneruje upozornění na tento kód.
Porozumění DynamicallyAccessedMembers
DynamicallyAccessedMembersAttribute řeší problém reflexe vytvořením explicitního kontraktu mezi volajícím a volanou metodou.
Základní účel
DynamicallyAccessedMembers řekne trimmeru: "Tento parametr (nebo pole nebo návratová hodnota) bude obsahovat Type, který potřebuje mít zachované konkrétní členy, protože k nim bude přistupováno pomocí reflexe."
Konkrétní příklad
Pojďme opravit předchozí příklad:
void PrintMethodNames(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}
}
// When this is called...
PrintMethodNames(typeof(DateTime));
Teď zastřihovač rozumí:
-
PrintMethodNamesvyžaduje, aby jeho parametrPublicMethodsbyl zachován. - Web volání předá
typeof(DateTime). -
DateTimeProto musí být zachovány veřejné metody.
Atribut vytvoří požadavek , který přetéká zpět od použití reflexe ke zdroji Type hodnoty.
Jedná se o kontrakt, nikoli nápovědu.
Je to důležité pro pochopení: DynamicallyAccessedMembers není to jenom dokumentace. Trimmer vynucuje tento kontrakt.
Analogie s omezeními obecného typu
Pokud znáte omezení obecného typu, DynamicallyAccessedMembers funguje podobně. Stejně jako obecná omezení procházejí kódem:
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 vytvoří podobné požadavky, které procházejí vaším kódem:
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
}
Oba vytvářejí kontrakty, které musí být splněny, a obě generují chyby nebo upozornění, když kontrakt nemůže být splněn.
Jak se smlouva vynucuje
[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());
}
Pokud smlouvu nemůžete splnit (například v druhém příkladu), zobrazí se upozornění.
Porozumění RequiresUnreferencedCode
Některé vzory kódu nelze jednoduše analyzovat staticky. V těchto případech použijte RequiresUnreferencedCodeAttribute.
Kdy použít RequiresUnreferencedCode
Atribut použijte v následujících RequiresUnreferencedCodeAttribute případech:
- Vzor reflexe je zásadně dynamický: Načítání sestavení nebo typů podle názvů řetězců z externích zdrojů.
- Složitost je příliš vysoká na anotace: Kód, který používá reflexi složitými způsoby řízenými daty.
-
Používáte generování kódu prostředí runtime: Technologie jako System.Reflection.Emit nebo
dynamicklíčové slovo.
Příklad:
[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
}
Účel atributu
RequiresUnreferencedCode slouží ke dvěma účelům:
- Potlačí upozornění uvnitř metody: Optimalizátor nebude analyzovat ani upozorňovat na použití reflexe.
- Vytvoří upozornění na webech volání: Jakýkoli kód volající tuto metodu dostane upozornění.
Toto upozornění zvýrazní vývojářům viditelnost do nekompatibilních cest kódu.
Psaní dobrých zpráv
Zpráva by měla vývojářům pomoct pochopit jejich možnosti:
// ❌ 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")]
Jak požadavky protékají kódem
Pochopení způsobu šíření požadavků vám pomůže zjistit, kde přidat atributy.
Tok požadavků zpět
Požadavky se šíří z místa, kde se používá odraz, zpět tam, odkud pochází 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
}
Aby bylo toto kompatibilní s oříznutím, musíte řetěz označit:
void ProcessData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
{
var type = typeof(T);
DisplayInfo(type);
}
void DisplayInfo(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
type.GetMethods();
}
Nyní tok požadavků: GetMethods() vyžaduje PublicMethods → type parametr vyžaduje PublicMethods → obecný T vyžaduje PublicMethods → DateTime vyžaduje zachování PublicMethods.
Požadavky procházející úložištěm
Požadavky také protékají poli a vlastnostmi:
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
}
}
Volba správného přístupu
Když narazíte na kód, který potřebuje reflexi, postupujte podle tohoto rozhodovacího stromu:
1. Můžete se vyhnout reflexi?
Nejlepším řešením je vyhnout se reflexi, pokud je to možné:
// ❌ 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. Je typ známý v době kompilace?
Pokud je reflexe nezbytná, ale typy jsou známé, použijte DynamicallyAccessedMembers:
// ✅ Trim-compatible
void Serialize<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(T obj)
{
foreach (var prop in typeof(T).GetProperties())
{
// Serialize property
}
}
3. Je model v podstatě dynamický?
Pokud typy skutečně nejsou známé až do spuštění, použijte 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
}
Běžné vzory a řešení
Model: Metody továrny
// 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);
}
Vzor: Pluginové systémy
// 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
Klíčové poznatky
- Zatřižovač používá statickou analýzu – dokáže pochopit pouze cesty kódu viditelné v době kompilace.
- Reflexe přeruší statickou analýzu – zatřihovací modul nevidí, k čemu bude reflexe přistupovat za běhu.
- DynamickyAccessedMembers vytváří kontrakty – říká trimmeru, co musí být zachováno.
-
Požadavky proudí zpět – od využití reflexe zpět ke zdroji
Typehodnoty. - RequiresUnreferencedCode dokumentuje nekompatibilitu – použijte jej, když kód nelze analyzovat.
- Atributy nejsou jen nápovědy – trimovač vynucuje kontrakty a generuje varování, když nelze tyto kontrakty splnit.
Další kroky
- Oprava upozornění oříznutí – Použití těchto konceptů k vyřešení upozornění v kódu
- Příprava knihoven na oříznutí – Učinit knihovny kompatibilními s oříznutím
- Referenční informace k výstrahám o ořezání – podrobné informace o konkrétních upozorněních