Sdílet prostřednictvím


Oprava upozornění na ořezání

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í:

  1. Eliminovat reflexi - To je vždy nejlepší možnost, pokud je to možné.
  2. Používejte DynamicallyAccessedMembers – pokud jsou známé typy, upravte kód tak, aby byl kompatibilní s optimalizací.
  3. Použijte RequiresUnreferencedCode – pokud je skutečně dynamické, zdokumentujte nekompatibilitu.
  4. 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:

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:

  1. Ručně jste zajistili, že veškerý požadovaný kód zůstane zachován (prostřednictvím DynamicDependency nebo jiných mechanismů).
  2. Cesta kódu se nikdy nespustí v osekaných scénářích.
  3. 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:

  1. Najděte, kde se reflexe používá (například GetMethods()).
  2. Označte parametr této metody poznámkami.
  3. Type Sledujte hodnotu zpětně skrze všechna volání metod.
  4. 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í

  1. Začněte vlastním kódem – nejprve opravte upozornění v kódu, který řídíte.
  2. Použijte TrimmerSingleWarn, abyste zobrazili jednotlivá upozornění z balíčků.
  3. Zvažte, jestli je oříznutí vhodné pro vaši aplikaci.
  4. 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