Megosztás a következőn keresztül:


Bevezetés a vágási figyelmeztetések használatába

Elméletileg a vágás egyszerű: amikor közzétesz egy alkalmazást, a .NET SDK elemzi a teljes alkalmazást, és eltávolítja az összes nem használt kódot. Azonban nehéz lehet meghatározni, hogy mi nem használt, vagy pontosabban, mit használnak.

Az alkalmazások vágásakor bekövetkező viselkedésváltozások elkerülése érdekében a .NET SDK vágási figyelmeztetésekkel statikus elemzést biztosít a vágási kompatibilitásról. A vágógép vágási figyelmeztetéseket hoz létre, amikor olyan kódot talál, amely nem kompatibilis a vágással. A nem vágáskompatibilis kód viselkedési változásokat vagy akár összeomlásokat is okozhat az alkalmazásokban a vágás után. Ideális esetben a vágást használó alkalmazásoknak nem szabad vágási figyelmeztetéseket létrehozniuk. Ha vannak vágási figyelmeztetések, az alkalmazást a vágás után alaposan tesztelni kell, hogy ne változhasson a viselkedés.

Ez a cikk segít megérteni, hogy egyes minták miért okoznak vágási figyelmeztetéseket, és hogyan kezelhetők ezek a figyelmeztetések.

Példák vágási figyelmeztetésekre

A legtöbb C#-kód esetében egyszerűen meghatározható, hogy milyen kódot használnak, és milyen kódot nem használnak – a vágó végigvezetheti a metódushívásokat, a mező- és tulajdonsághivatkozásokat, és így tovább, és meghatározhatja, hogy milyen kódhoz fér hozzá. Sajnos egyes funkciók, például a tükröződés, jelentős problémát jelentenek. Tekintse meg az alábbi kódot:

string s = Console.ReadLine();
Type type = Type.GetType(s);
foreach (var m in type.GetMethods())
{
    Console.WriteLine(m.Name);
}

Ebben a példában GetType() dinamikusan kér egy ismeretlen nevű típust, majd kinyomtatja az összes metódus nevét. Mivel közzétételkor nem lehet tudni, hogy milyen típusnevet fog használni, a vágó nem tudja, hogy a kimenetben melyik típust kell megőrizni. Valószínű, hogy ez a kód a vágás előtt működött volna (feltéve, hogy a bemeneti adatok ismertek a cél keretrendszerben), de valószínűleg null hivatkozási kivételt eredményezne a vágás után, mivel Type.GetType null értéket ad vissza, ha a típus nem található.

Ebben az esetben a vágó egy figyelmeztetést ad ki a híváshoz Type.GetType, jelezve, hogy nem tudja meghatározni, hogy az alkalmazás melyik típust fogja használni.

Reagálás a vágási figyelmeztetésekre

A vágási figyelmeztetések célja, hogy kiszámíthatóvá tehesse a vágást. A figyelmeztetéseknek két nagy kategóriája van, amelyeket valószínűleg látni fog:

  1. A funkciók nem kompatibilisek a vágással
  2. A funkció bizonyos követelményekkel rendelkezik a vágáskompatibilis bemenetre vonatkozóan

A funkció nem kompatibilis a vágással

Ezek általában olyan metódusok, amelyek egyáltalán nem működnek, vagy egyes esetekben hibásak lehetnek, ha egy levágott alkalmazásban használják őket. Jó példa az Type.GetType előző példában szereplő módszer. A levágott alkalmazásokban ez működik, de nincs garancia. Az ilyen API-k a következővel RequiresUnreferencedCodeAttributevannak megjelölve: .

RequiresUnreferencedCodeAttribute egyszerű és széles: ez egy attribútum, amely azt jelenti, hogy a tag nem kompatibilis a vágással. Ezt az attribútumot akkor használja a rendszer, ha a kód alapvetően nem vágáskompatibilis, vagy a vágási függőség túl összetett ahhoz, hogy elmagyarázza a vágónak. Ez gyakran igaz azokra a metódusokra, amelyek dinamikusan töltik be a kódot például egy alkalmazás vagy szerelvény összes típusán keresztül LoadFrom(String), például GetType()a C# dynamic kulcsszó használatával vagy más futtatókörnyezeti kódgenerálási technológiák használatával. Ilyen például a következő:

[RequiresUnreferencedCode("This functionality is not compatible with trimming. Use 'MethodFriendlyToTrimming' instead")]
void MethodWithAssemblyLoad()
{
    ...
    Assembly.LoadFrom(...);
    ...
}

void TestMethod()
{
    // IL2026: Using method 'MethodWithAssemblyLoad' which has 'RequiresUnreferencedCodeAttribute'
    // can break functionality when trimming application code. This functionality is not compatible with trimming. Use 'MethodFriendlyToTrimming' instead.
    MethodWithAssemblyLoad();
}

Nincs sok áthidaló megoldás a RequiresUnreferencedCode. A legjobb megoldás, ha egyáltalán nem hívja meg a metódust a vágáskor, és használjon más, vágáskompatibilis megoldást.

A funkció megjelölése a vágással nem kompatibilisként

Ha kódtárat ír, és nincs az Ön ellenőrzése alatt, hogy nem kompatibilis funkciót használ-e, megjelölheti a következővel RequiresUnreferencedCode: . Ez a megjegyzés a metódust a vágással nem kompatibilisnek tekinti. A csöndek használata RequiresUnreferencedCode az adott módszer összes vágási figyelmeztetését elnémítja, de figyelmeztetést ad, amikor valaki más hívja.

Ehhez RequiresUnreferencedCodeAttribute meg kell adnia egy Message. Az üzenet a megjelölt metódust hívó fejlesztőnek jelentett figyelmeztetés részeként jelenik meg. Példa:

IL2026: Using member <incompatible method> which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. <The message value>

A fenti példában egy adott metódusra vonatkozó figyelmeztetés a következőképpen nézhet ki:

IL2026: Using member 'MethodWithAssemblyLoad()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. This functionality is not compatible with trimming. Use 'MethodFriendlyToTrimming' instead.

Az ilyen API-kat hívó fejlesztők általában nem fogják érdekelni az érintett API-k vagy a vágással kapcsolatos konkrétumok.

Egy jó üzenetben meg kell jelölni, hogy mely funkciók nem kompatibilisek a vágással, majd útmutatást kell adnia a fejlesztőnek a lehetséges következő lépésekhez. Javasolhatja egy másik funkció használatát, vagy módosíthatja a funkció használatát. Azt is egyszerűen kijelentheti, hogy a funkció még nem kompatibilis a vágással egyértelmű csere nélkül.

Ha a fejlesztő útmutatása túl hosszú lesz ahhoz, hogy belefoglaljon egy figyelmeztető üzenetbe, hozzáadhat egy nem kötelezőt Url , amely a RequiresUnreferencedCodeAttribute fejlesztőt egy olyan weblapra irányítja, amely részletesebben ismerteti a problémát és a lehetséges megoldásokat.

Példa:

[RequiresUnreferencedCode("This functionality is not compatible with trimming. Use 'MethodFriendlyToTrimming' instead", Url = "https://site/trimming-and-method")]
void MethodWithAssemblyLoad() { ... }

Ez figyelmeztetést eredményez:

IL2026: Using member 'MethodWithAssemblyLoad()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. This functionality is not compatible with trimming. Use 'MethodFriendlyToTrimming' instead. https://site/trimming-and-method

A gyakran használt RequiresUnreferencedCode módszerekkel több metódust is megjelölhet ugyanazzal az okból. Ez akkor gyakori, ha egy magas szintű metódus nem kompatibilis a vágással, mert olyan alacsony szintű metódust hív meg, amely nem trim-kompatibilis. A figyelmeztetést "felbuborozza" egy nyilvános API-ra. Minden egyes használathoz RequiresUnreferencedCode üzenetre van szükség, és ezekben az esetekben az üzenetek valószínűleg megegyeznek. A sztringek duplikálásának elkerülése és a könnyebb karbantartás érdekében használjon állandó sztringmezőt az üzenet tárolásához:

class Functionality
{
    const string IncompatibleWithTrimmingMessage = "This functionality is not compatible with trimming. Use 'FunctionalityFriendlyToTrimming' instead";

    [RequiresUnreferencedCode(IncompatibleWithTrimmingMessage)]
    private void ImplementationOfAssemblyLoading()
    {
        ...
    }

    [RequiresUnreferencedCode(IncompatibleWithTrimmingMessage)]
    public void MethodWithAssemblyLoad()
    {
        ImplementationOfAssemblyLoading();
    }
}

A bemenetre vonatkozó követelményekkel rendelkező funkciók

A vágás api-kat biztosít a metódusok és más tagok bemenetére vonatkozó további követelmények megadásához, amelyek vágáskompatibilis kódhoz vezetnek. Ezek a követelmények általában a tükrözésről szólnak, és arról, hogy bizonyos tagokhoz vagy műveletekhez milyen típusú hozzáférés érhető el. Ezek a követelmények a DynamicallyAccessedMembersAttribute.

A RequiresUnreferencedCodevisszatükrözést a vágó néha megértheti, amíg helyesen van széljegyzete. Vessünk egy másik pillantást az eredeti példára:

string s = Console.ReadLine();
Type type = Type.GetType(s);
foreach (var m in type.GetMethods())
{
    Console.WriteLine(m.Name);
}

Az előző példában a valódi probléma a következő Console.ReadLine(): . Mivel bármilyen típus olvasható, a vágógép nem tudja, hogy szükség van-e metódusokra, vagy System.Guid bármilyen más típusraSystem.DateTime. Másrészt a következő kód rendben lenne:

Type type = typeof(System.DateTime);
foreach (var m in type.GetMethods())
{
    Console.WriteLine(m.Name);
}

Itt a vágó láthatja a pontos típusra hivatkozva: System.DateTime. Most már használhatja a folyamatelemzést annak meghatározására, hogy az összes nyilvános metódust be kell tartania System.DateTime. Szóval, hol DynamicallyAccessMembers jön be? Ha a tükröződés több metódusra oszlik. Az alábbi kódban láthatjuk, hogy a típus System.DateTime olyan helyre Method3 áramlik, ahol a tükröződés a metódusok elérésére System.DateTimeszolgál,

void Method1()
{
    Method2<System.DateTime>();
}
void Method2<T>()
{
    Type t = typeof(T);
    Method3(t);
}
void Method3(Type type)
{
    var methods = type.GetMethods();
    ...
}

Ha az előző kódot fordítja le, a következő figyelmeztetés jön létre:

IL2070: Program.Method3(Type): Az "this" argumentum nem felel meg a "DynamicallyAccessedMemberTypes.PublicMethods" kifejezésnek a System.Type.GetMethods()" hívásban. A Program.Method3(Type)" metódus "type" paramétere nem rendelkezik megfelelő széljegyzetekkel. A forrásértéknek legalább ugyanazokat a követelményeket kell deklarálnia, mint amelyek a hozzárendelt célhelyen vannak deklarálva.

A teljesítmény és a stabilitás érdekében a folyamatelemzés nem történik meg a metódusok között, ezért a metódusok közötti információk továbbításához megjegyzésre van szükség a visszaverődési hívástól (GetMethods) a Typeforrásig. Az előző példában a vágó figyelmeztetés azt mondja, hogy GetMethods a Type meghívott objektumpéldánynak szüksége van a PublicMethods széljegyzetre, de a type változónak nincs ugyanaz a követelménye. Más szóval a hívótól a hívóig GetMethods át kell adnunk a követelményeket:

void Method1()
{
    Method2<System.DateTime>();
}
void Method2<T>()
{
    Type t = typeof(T);
    Method3(t);
}
void Method3(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    var methods = type.GetMethods();
  ...
}

A paraméter typemegjegyzése után az eredeti figyelmeztetés eltűnik, de megjelenik egy másik:

IL2087: A "type" argumentum nem felel meg a "DynamicallyAccessedMemberTypes.PublicMethods" argumentumnak a Program.Method3(Type)" hívásban. A Program.Method2<T>()" általános "T" paramétere nem rendelkezik egyező széljegyzetekkel.

A széljegyzeteket a paraméterig typeMethod3propagáltuk, ebben Method2 egy hasonló probléma merült fel. A vágó képes nyomon követni az értéket T a hívás typeofsorán, hozzárendelve a helyi változóhoz t, és átadja a következőnek Method3: . Ekkor azt látja, hogy a paraméternek type szüksége PublicMethods van rá, de nincsenek követelmények, Tés új figyelmeztetést hoz létre. Ennek kijavításához "széljegyzeteket kell fűznünk és propagálni" úgy, hogy a hívásláncon végig széljegyzeteket alkalmazunk, amíg el nem érünk egy statikusan ismert típust (például System.DateTime vagy System.Tuple), vagy egy másik jegyzettel ellátott értéket. Ebben az esetben megjegyzést kell fűznünk a típusparaméterhez TMethod2.

void Method1()
{
    Method2<System.DateTime>();
}
void Method2<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>()
{
    Type t = typeof(T);
    Method3(t);
}
void Method3(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    var methods = type.GetMethods();
  ...
}

Most nincsenek figyelmeztetések, mert a vágó tudja, hogy mely tagok érhetők el futásidejű tükröződés (nyilvános metódusok) és milyen típusok (System.DateTime), és megőrzi őket. Ajánlott széljegyzeteket hozzáadni, hogy a vágó tudja, mit őrizze meg.

A további követelmények által generált figyelmeztetések automatikusan el lesznek tiltva, ha az érintett kód egy olyan metódusban van, amely a következővel rendelkezik RequiresUnreferencedCode: .

Az RequiresUnreferencedCodeinkompatibilitást egyszerűen jelenti, de a hozzáadás DynamicallyAccessedMembers kompatibilissé teszi a kódot a vágással.

A vágóra vonatkozó figyelmeztetések mellőzése

Ha valahogy meg tudja állapítani, hogy a hívás biztonságos, és a szükséges kód nem lesz levágva, a figyelmeztetést is letilthatja a használatával UnconditionalSuppressMessageAttribute. Példa:

[RequiresUnreferencedCode("Use 'MethodFriendlyToTrimming' instead")]
void MethodWithAssemblyLoad() { ... }

[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode",
    Justification = "Everything referenced in the loaded assembly is manually preserved, so it's safe")]
void TestMethod()
{
    InitializeEverything();

    MethodWithAssemblyLoad(); // Warning suppressed

    ReportResults();
}

Figyelmeztetés

Legyen nagyon óvatos, amikor letiltja a vágási figyelmeztetéseket. Lehetséges, hogy a hívás most már nem kompatibilis, de ha módosítja a kódot, amely megváltozhat, és elfelejtheti áttekinteni az összes letiltást.

UnconditionalSuppressMessage hasonló SuppressMessage , de látható, publish és más utólagos eszközök.

Fontos

Ne használja SuppressMessage és #pragma warning disable ne tiltsa le a vágó figyelmeztetéseit. Ezek csak a fordító számára működnek, de nem maradnak meg a lefordított szerelvényben. A Trimmer lefordított szerelvényeken működik, és nem látja az elnyomást.

Az elnyomás a teljes metódustörzsre vonatkozik. A fenti mintában tehát elfojtja a metódus összes IL2026 figyelmeztetését. Ez megnehezíti a megértést, mivel nem egyértelmű, hogy melyik módszer a problémás, hacsak nem ad hozzá megjegyzést. Ennél is fontosabb, hogy ha a kód a jövőben megváltozik, például ha ReportResults a trim-inkompatibilissé válik, a rendszer nem jelent figyelmeztetést ehhez a metódushíváshoz.

Ezt úgy oldhatja meg, hogy a problémás metódushívást egy másik metódusba vagy helyi függvénybe újrabontással, majd az elnyomás csak az adott metódusra alkalmazza:

void TestMethod()
{
    InitializeEverything();

    CallMethodWithAssemblyLoad();

    ReportResults();

    [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode",
        Justification = "Everything referenced in the loaded assembly is manually preserved, so it's safe")]
    void CallMethodWithAssemblyLoad()
    {
        MethodWIthAssemblyLoad(); // Warning suppressed
    }
}