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.
Když ve své aplikaci povolíte oříznutí, sada .NET SDK provede statickou analýzu a detekuje vzory kódu, které nemusí být kompatibilní s oříznutím. Upozornění oříznutí naznačují potenciální problémy, které by mohly způsobit změny chování nebo zhroucení aplikace po oříznutí.
Aplikace, která používá oříznutí, by neměla vygenerovat žádná upozornění na oříznutí. Pokud existují nějaká upozornění na oříznutí, důkladně otestujte aplikaci po oříznutí, abyste měli jistotu, že nedošlo ke změnám chování.
Tento článek obsahuje praktické pracovní postupy pro řešení varování o ořezání. Podrobnější informace o tom, proč k těmto upozorněním dochází a jak funguje oříznutí, najdete v tématu Vysvětlení analýzy oříznutí.
Pochopení kategorií upozornění
Výstrahy oříznutí spadají do dvou hlavních kategorií:
Kód nekompatibilní s oříznutím – označený znakem RequiresUnreferencedCodeAttribute. Kód v zásadě nemůže být analyzovatelný (například dynamické načítání knihoven nebo složité vzory reflexe). Metoda je označena jako nekompatibilní a volající obdrží upozornění.
Kód s požadavky – opatřený poznámkami DynamicallyAccessedMembersAttribute. Používá se reflexe, ale typy jsou známé v době kompilace. Jakmile jsou splněny požadavky, kód se stane plně kompatibilním se střihem.
Pracovní postup: Určení správného přístupu
Když narazíte na varování o oříznutí, postupujte podle těchto kroků v pořadí:
- Eliminovat reflexi - To je vždy nejlepší možnost, pokud je to možné.
- Používejte DynamicallyAccessedMembers – pokud jsou známé typy, upravte kód tak, aby byl kompatibilní s optimalizací.
- Použijte RequiresUnreferencedCode – pokud je skutečně dynamické, zdokumentujte nekompatibilitu.
- Potlačit upozornění jako poslední možnost – pouze pokud jste si jistí, že je kód bezpečný.
Přístup 1: Eliminace reflexe
Nejlepším řešením je vyhnout se zcela reflexi, pokud je to možné. Díky tomu je váš kód rychlejší a plně kompatibilní s funkcí 'trim'.
Použití generik za běhu kompilace
Nahraďte operace typu runtime obecnými parametry v čase kompilace:
// ❌ 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...
}
Použití generátorů zdrojů
Moderní .NET poskytuje generátory zdrojů pro běžné scénáře reflexe:
- Serializace: Místo serializátorů založených na reflexi použijte generování zdroje System.Text.Json .
- Konfigurace: Použijte generátor konfigurace pro zdroj vazby
Další informace najdete v tématu Známé nekompatibility oříznutí.
Přístup 2: Zajištění kompatibility kódu s dynamickyaccessedMembers
Pokud je potřeba reflexe, ale typy jsou známy během kompilace, použijte DynamicallyAccessedMembersAttribute k dosažení trim-kompatibility vašeho kódu.
Krok za krokem: Anotace použití reflexe
Podívejte se na tento příklad, který vygeneruje upozornění:
void PrintMethodNames(Type type)
{
// ⚠️ IL2070: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods'
foreach (var method in type.GetMethods())
{
Console.WriteLine(method.Name);
}
}
Krok 1: Zjištění, jaká operace reflexe se provádí
Volání kódu GetMethods(), které vyžaduje zachování PublicMethods.
Krok 2: Přidávání poznámek k parametru
Přidejte DynamicallyAccessedMembers , abyste trimmeru řekli, co je potřeba.
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);
}
}
Krok 3: Zajištění, aby volající splňovali požadavek
Při volání této metody se známým typem (typeof) je požadavek automaticky splněn:
// ✅ OK - DateTime's public methods will be preserved
PrintMethodNames(typeof(DateTime));
Podrobný postup: Šíření požadavků prostřednictvím řetězů volání
Pokud typy procházejí více metodami, je potřeba rozšířit požadavky:
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
}
Krok 1: Začínáme s využitím reflexe
Anotace, kde se reflexe skutečně používá:
void Method3(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
var methods = type.GetMethods(); // ✅ Fixed
}
Krok 2: Rozšíření řetězu volání
Projděte zpětně řetězec volání:
void Method2<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
{
Type t = typeof(T);
Method3(t); // ✅ Fixed - T is annotated
}
Krok 3: Ověřte na místě volání
void Method1()
{
Method2<DateTime>(); // ✅ Fixed - DateTime's public methods preserved
}
Další podrobnosti o tom, jak požadavky procházejí kódem, najdete viz Analýza trimu.
Běžné hodnoty DynamickyAccessedMemberTypes
Zvolte minimální potřebnou úroveň přístupu:
| Typ členu | Kdy používat |
|---|---|
PublicConstructors |
Použití Activator.CreateInstance() nebo GetConstructor() |
PublicMethods |
Použití GetMethod() nebo GetMethods() |
PublicFields |
Použití GetField() nebo GetFields() |
PublicProperties |
Použití GetProperty() nebo GetProperties() (serializace) |
PublicEvents |
Použití GetEvent() nebo GetEvents() |
Upozorňující
Použití DynamicallyAccessedMemberTypes.All zachovává všechny členy cílového typu a všechny členy v jeho vnořených typech (ale ne tranzitivní závislosti, jako jsou členy návratového typu vlastnosti). Tím se výrazně zvětšuje velikost aplikace. Důležitější je, že zachované členy se stanou dostupnými, což znamená, že mohou obsahovat vlastní problematický kód. Pokud například zachovaný člen volá metodu označenou jako RequiresUnreferencedCode, nelze toto upozornění vyřešit, protože člen je udržován prostřednictvím anotace reflexe místo explicitního volání. Pokud se chcete těmto kaskádým problémům vyhnout, použijte minimální požadované typy členů.
Přístup 3: Označení kódu jako nekompatibilní s RequiresUnreferencedCode
Pokud kód zásadně nelze učinit analyzovatelným, použijte RequiresUnreferencedCodeAttribute k dokumentaci nekompatibility.
Kdy použít RequiresUnreferencedCode
Tento atribut použijte v těchto případech:
- Typy se načítají dynamicky: Používá se GetType() s řetězci určenými modulem runtime.
- Sestavení jsou načtena za běhu: Pomocí LoadFrom(String).
- Složité vzory reflexe: Použití reflexe je příliš složité pro přidávání poznámek.
-
Generování kódu za běhu: Použití System.Reflection.Emit nebo klíčové slovo
dynamic
Podrobné pokyny: Označení nekompatibilních metod
Krok 1: Identifikace skutečně nekompatibilního kódu
Příklad kódu, který nelze přizpůsobit kompatibilitě s funkcí 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...
}
Krok 2: Přidání atributu 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
}
Krok 3: Volajícím se zobrazí upozornění
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");
}
Psaní efektivních zpráv s upozorněním
Dobrá RequiresUnreferencedCode zpráva by měla:
- Uveďte, jaké funkce nejsou kompatibilní: Buďte konkrétní, co nefunguje s ořezáváním.
- Navrhněte alternativy: Veďte vývojáře k řešením, která jsou kompatibilní s trimem.
- Buďte struční: Udržujte zprávy krátké a použitelné.
// ❌ 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.")]
Pokud chcete delší pokyny, přidejte Url parametr:
[RequiresUnreferencedCode(
"Plugin system is not compatible with trimming. See documentation for alternatives.",
Url = "https://docs.example.com/plugin-trimming")]
Šíření RequiresUnreferencedCode
Když metoda volá jinou metodu označenou RequiresUnreferencedCode, obvykle je nutné rozšířit atribut:
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
}
}
Běžné vzory a řešení
Model: Metody továrny s aktivátorem.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);
}
Vzorec: Plug-in systémy načítající sestavení
// 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);
}
}
Vzorec: Kontejnery vkládání závislostí
// 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
}
}
Přístup 4: Potlačení upozornění jako posledního řešení
Upozorňující
Pokud jste si naprosto jistí, že je kód bezpečný, měli byste potlačit pouze varování o oříznutí. Nesprávné potlačení může po oříznutí vést k chybám za běhu.
Použijte UnconditionalSuppressMessageAttribute, pokud jste ověřili, že kód je bezpečný pro ořez, ale nástroj pro ořez to nemůže staticky dokázat.
Je-li potlačení vhodné
Potlačit upozornění pouze v případech:
- Ručně jste zajistili, že veškerý požadovaný kód zůstane zachován (prostřednictvím
DynamicDependencynebo jiných mechanismů). - Cesta kódu se nikdy nespustí v osekaných scénářích.
- Důkladně jste otestovali optimalizovanou aplikaci.
Potlačení upozornění
[RequiresUnreferencedCode("Uses reflection")]
void MethodWithReflection() { /* ... */ }
[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
Justification = "All referenced types are manually preserved via DynamicDependency attributes")]
void CallerMethod()
{
MethodWithReflection(); // Warning suppressed
}
Důležité
Nepoužívejte SuppressMessage nebo #pragma warning disable pro upozornění na oříznutí. Tyto funkce fungují pouze pro kompilátor, ale v kompilovaném sestavení se nezachovají. Zastřihovač pracuje na kompilovaných sestaveních a tato potlačení neuvidí. Vždy používejte UnconditionalSuppressMessage.
Minimalizace rozsahu potlačení
Aplikujte potlačení na co nejmenší možný rozsah. Extrahujte problematické volání do místní funkce:
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();
}
}
Tento přístup:
- Ujasňuje, které konkrétní volání je potlačeno.
- Zabrání náhodnému potlačení jiných upozornění, pokud se kód změní.
- Zarovnání zůstane blízko potlačenému hovoru.
Tipy pro řešení potíží
Upozornění přetrvává po přidání DynamicallyAccessedMembers
Ujistěte se, že jste celý řetězec volání správně anotovali od použití reflexe až zpět ke zdroji Type:
- Najděte, kde se reflexe používá (například
GetMethods()). - Označte parametr této metody poznámkami.
-
TypeSledujte hodnotu zpětně skrze všechna volání metod. - Označte každý parametr, pole nebo parametr obecného typu v řetězu poznámkami.
Příliš mnoho upozornění k vyřešení
- Začněte vlastním kódem – nejprve opravte upozornění v kódu, který řídíte.
- Použijte
TrimmerSingleWarn, abyste zobrazili jednotlivá upozornění z balíčků. - Zvažte, jestli je oříznutí vhodné pro vaši aplikaci.
- Zkontrolujte známé nekompatibility při trimování pro problémy na úrovni frameworku.
Nejste si jisti, které DynamicallyAccessedMemberTypes použít
Podívejte se na volané rozhraní API reflexe:
-
GetMethod()/GetMethods()→PublicMethods -
GetProperty()/GetProperties()→PublicProperties -
GetField()/GetFields()→PublicFields -
GetConstructor()/Activator.CreateInstance()PublicParameterlessConstructor→ neboPublicConstructors -
GetEvent()/GetEvents()→PublicEvents
Pokud chcete minimalizovat velikost aplikace, použijte nejužší možný typ.
Další kroky
- Principy analýzy oříznutí – Seznamte se se základními koncepty, které stojí za upozorněními oříznutí
- Příprava knihoven na oříznutí – Zajištění kompatibility knihoven
- Referenční informace o upozorněních na oříznutí – podrobné informace o specifických kódech varování
- Známé nekompatibility – vzory, které nelze upravit jako vyhovující střihu