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. A vágást használó alkalmazások nem hozhatnak létre vágási figyelmeztetéseket. 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:
- A funkciók nem kompatibilisek a vágással
- 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 RequiresUnreferencedCode
visszatü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.DateTime
szolgá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 Type
forrá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 type
megjegyzé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 type
Method3
propagá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 typeof
sorá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 T
Method2
.
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 RequiresUnreferencedCode
inkompatibilitást egyszerűen jelenti, de a hozzáadás DynamicallyAccessedMembers
kompatibilissé teszi a kódot a vágással.
Feljegyzés
A használat DynamicallyAccessedMembersAttribute
a típus összes megadott DynamicallyAccessedMemberTypes
tagját gyökerezteti. Ez azt jelenti, hogy megtartja a tagokat, valamint a tagok által hivatkozott metaadatokat. Ez a vártnál sokkal nagyobb alkalmazásokat eredményezhet. Ügyeljen arra, hogy a minimálisan szükségest DynamicallyAccessedMemberTypes
használja.
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
}
}