Inleiding tot trimwaarschuwingen
Conceptueel is bijsnijden eenvoudig: wanneer u een toepassing publiceert, analyseert de .NET SDK de hele toepassing en verwijdert u alle ongebruikte code. Het kan echter moeilijk zijn om te bepalen wat ongebruikt is, of nauwkeuriger, wat wordt gebruikt.
Om te voorkomen dat wijzigingen in gedrag optreden bij het bijsnijden van toepassingen, biedt de .NET SDK statische analyse van trimcompatibiliteit via trimwaarschuwingen. De trimmer produceert trimwaarschuwingen wanneer er code wordt gevonden die mogelijk niet compatibel is met bijsnijden. Code die niet compatibel is met trim, kan gedragswijzigingen of zelfs crashes veroorzaken in een toepassing nadat deze is ingekort. In het ideale geval mogen alle toepassingen die bijsnijden gebruiken geen trimwaarschuwingen produceren. Als er trimwaarschuwingen zijn, moet de app grondig worden getest na het bijsnijden om ervoor te zorgen dat er geen gedragswijzigingen zijn.
Dit artikel helpt u te begrijpen waarom sommige patronen trimwaarschuwingen produceren en hoe deze waarschuwingen kunnen worden aangepakt.
Voorbeelden van trimwaarschuwingen
Voor de meeste C#-code is het eenvoudig om te bepalen welke code wordt gebruikt en welke code niet wordt gebruikt. De trimmer kan methodeaanroepen, veld- en eigenschapsverwijzingen doorlopen, enzovoort, en bepalen welke code wordt geopend. Helaas vormen sommige functies, zoals weerspiegeling, een belangrijk probleem. Kijk eens naar de volgende code:
string s = Console.ReadLine();
Type type = Type.GetType(s);
foreach (var m in type.GetMethods())
{
Console.WriteLine(m.Name);
}
In dit voorbeeld GetType() wordt dynamisch een type met een onbekende naam aangevraagd en worden vervolgens de namen van alle methoden afgedrukt. Omdat er op het moment van publiceren geen manier is om te weten welke typenaam wordt gebruikt, is er geen manier om de trimmer te laten weten welk type in de uitvoer moet worden bewaard. Het is waarschijnlijk dat deze code had kunnen werken voordat deze werd ingekort (zolang de invoer iets is dat bekend is in het doelframework), maar waarschijnlijk een null-verwijzingsondering zou produceren na het bijsnijden, omdat Type.GetType
null wordt geretourneerd wanneer het type niet wordt gevonden.
In dit geval geeft de trimmer een waarschuwing voor de aanroep uit Type.GetType
, waarmee wordt aangegeven dat het niet kan bepalen welk type door de toepassing moet worden gebruikt.
Reageren op trimwaarschuwingen
Trimwaarschuwingen zijn bedoeld om voorspelbaarheid voor bijsnijden te brengen. Er zijn twee grote categorieën waarschuwingen die u waarschijnlijk zult zien:
- Functionaliteit is niet compatibel met bijsnijden
- Functionaliteit heeft bepaalde vereisten voor de invoer om compatibel te zijn met trim
Functionaliteit is niet compatibel met bijsnijden
Dit zijn meestal methoden die helemaal niet werken of in sommige gevallen kunnen worden verbroken als ze worden gebruikt in een bijgesneden toepassing. Een goed voorbeeld is de Type.GetType
methode uit het vorige voorbeeld. In een bijgesneden app werkt het misschien wel, maar er is geen garantie. Dergelijke API's zijn gemarkeerd met RequiresUnreferencedCodeAttribute.
RequiresUnreferencedCodeAttribute is eenvoudig en breed: het is een kenmerk dat betekent dat het lid niet compatibel is met bijsnijden. Dit kenmerk wordt gebruikt wanneer code fundamenteel niet compatibel is met trim of de afhankelijkheid voor bijsnijden te complex is om aan de trimmer uit te leggen. Dit geldt vaak voor methoden die code dynamisch laden, bijvoorbeeld via LoadFrom(String), inventariseren of doorzoeken van alle typen in een toepassing of assembly, bijvoorbeeld via GetType(), het C# dynamic
-trefwoord gebruiken of andere technologieën voor het genereren van runtimecode gebruiken. Een voorbeeld hiervan is:
[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();
}
Er zijn niet veel tijdelijke oplossingen voor RequiresUnreferencedCode
. De beste oplossing is om te voorkomen dat u de methode helemaal aanroept bij het bijsnijden en iets anders gebruikt dat compatibel is met trim.
Functionaliteit markeren als niet compatibel met bijsnijden
Als u een bibliotheek schrijft en deze niet in uw besturingselement staat of u incompatibele functionaliteit wilt gebruiken, kunt u deze markeren met RequiresUnreferencedCode
. Hiermee wordt uw methode incompatibel met bijsnijden. Als u stiltes gebruikt RequiresUnreferencedCode
, worden alle knipwaarschuwingen in de opgegeven methode gestild, maar wordt een waarschuwing gegenereerd wanneer iemand anders deze aanroept.
Hiervoor RequiresUnreferencedCodeAttribute moet u een Message
. Het bericht wordt weergegeven als onderdeel van een waarschuwing die wordt gerapporteerd aan de ontwikkelaar die de gemarkeerde methode aanroept. Voorbeeld:
IL2026: Using member <incompatible method> which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. <The message value>
In het bovenstaande voorbeeld kan een waarschuwing voor een specifieke methode er als volgt uitzien:
IL2026: Using member 'MethodWithAssemblyLoad()' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. This functionality is not compatible with trimming. Use 'MethodFriendlyToTrimming' instead.
Ontwikkelaars die dergelijke API's aanroepen, zijn over het algemeen niet geïnteresseerd in de details van de betrokken API of specifieke gegevens, omdat deze betrekking hebben op bijsnijden.
Een goed bericht moet aangeven welke functionaliteit niet compatibel is met bijsnijden en de ontwikkelaar vervolgens begeleiden wat de mogelijke volgende stappen zijn. Het kan voorstellen om een andere functionaliteit te gebruiken of de manier te wijzigen waarop de functionaliteit wordt gebruikt. Het kan ook gewoon aangeven dat de functionaliteit nog niet compatibel is met bijsnijden zonder een duidelijke vervanging.
Als de richtlijnen voor de ontwikkelaar te lang worden om te worden opgenomen in een waarschuwingsbericht, kunt u een optioneel Url
toevoegen aan de RequiresUnreferencedCodeAttribute ontwikkelaar naar een webpagina met een beschrijving van het probleem en mogelijke oplossingen.
Voorbeeld:
[RequiresUnreferencedCode("This functionality is not compatible with trimming. Use 'MethodFriendlyToTrimming' instead", Url = "https://site/trimming-and-method")]
void MethodWithAssemblyLoad() { ... }
Hiermee wordt een waarschuwing gegenereerd:
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
Het gebruik leidt RequiresUnreferencedCode
vaak tot het markeren van meer methoden, vanwege dezelfde reden. Dit komt vaak voor wanneer een methode op hoog niveau niet compatibel is met bijsnijden, omdat deze een methode op laag niveau aanroept die niet compatibel is met trim. U 'belt' de waarschuwing voor een openbare API op. Elk gebruik van RequiresUnreferencedCode
een bericht heeft een bericht nodig en in deze gevallen zijn de berichten waarschijnlijk hetzelfde. Als u wilt voorkomen dat tekenreeksen worden gedupliceerd en eenvoudiger te onderhouden, gebruikt u een constant tekenreeksveld om het bericht op te slaan:
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();
}
}
Functionaliteit met vereisten voor de invoer
Bijsnijden biedt API's om meer vereisten op te geven voor invoer voor methoden en andere leden die leiden tot bijsnijden compatibele code. Deze vereisten gaan meestal over reflectie en de mogelijkheid om toegang te krijgen tot bepaalde leden of bewerkingen op een type. Dergelijke vereisten worden opgegeven met behulp van de DynamicallyAccessedMembersAttribute.
In tegenstelling tot RequiresUnreferencedCode
, kan weerspiegeling soms worden begrepen door de trimmer zolang deze correct is geannoteerd. Laten we nog eens kijken naar het oorspronkelijke voorbeeld:
string s = Console.ReadLine();
Type type = Type.GetType(s);
foreach (var m in type.GetMethods())
{
Console.WriteLine(m.Name);
}
In het vorige voorbeeld is Console.ReadLine()
het echte probleem. Omdat elk type kan worden gelezen, kan de trimmer niet weten of u methoden System.DateTime
nodig hebt voor of System.Guid
een ander type. Aan de andere kant zou de volgende code prima zijn:
Type type = typeof(System.DateTime);
foreach (var m in type.GetMethods())
{
Console.WriteLine(m.Name);
}
Hier kan de trimmer het exacte type zien waarnaar wordt verwezen: System.DateTime
. Nu kan de stroomanalyse worden gebruikt om te bepalen of alle openbare methoden moeten worden bewaard System.DateTime
. Waar komt er dan DynamicallyAccessMembers
binnen? Wanneer weerspiegeling wordt verdeeld over meerdere methoden. In de volgende code zien we dat het type System.DateTime
stroomt naar Method3
waar weerspiegeling wordt gebruikt voor toegang tot System.DateTime
de methoden,
void Method1()
{
Method2<System.DateTime>();
}
void Method2<T>()
{
Type t = typeof(T);
Method3(t);
}
void Method3(Type type)
{
var methods = type.GetMethods();
...
}
Als u de vorige code compileert, wordt de volgende waarschuwing gegenereerd:
IL2070: Program.Method3(Type): 'this' argument voldoet niet aan DynamicallyAccessedMemberTypes.PublicMethods in aanroep van System.Type.GetMethods(). De parameter 'type' van methode 'Program.Method3(Type)' heeft geen overeenkomende aantekeningen. De bronwaarde moet ten minste dezelfde vereisten declareren als die zijn gedeclareerd op de doellocatie waaraan deze is toegewezen.
Voor prestaties en stabiliteit wordt stroomanalyse niet uitgevoerd tussen methoden, dus er is een aantekening nodig om informatie door te geven tussen methoden, van de weerspiegelingsaanroep (GetMethods
) naar de bron van de Type
. In het vorige voorbeeld zegt de trimmerwaarschuwing dat vereist dat GetMethods
het Type
objectexemplaren de aantekening hebben PublicMethods
, maar dat de type
variabele niet dezelfde vereiste heeft. Met andere woorden, we moeten de vereisten van GetMethods
de aanroeper doorgeven:
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();
...
}
Na het toevoegen van aantekeningen aan de parameter type
verdwijnt de oorspronkelijke waarschuwing, maar wordt er nog een weergegeven:
IL2087: 'type' argument voldoet niet aan 'DynamicallyAccessedMemberTypes.PublicMethods' in aanroep 'Program.Method3(Type)'. De algemene parameter 'T' van 'Program.Method2<T>()' heeft geen overeenkomende aantekeningen.
We hebben aantekeningen doorgegeven tot de parameter type
van Method3
, in Method2
we hebben een vergelijkbaar probleem. De trimmer kan de waarde T
bijhouden terwijl deze door de aanroep loopt typeof
, wordt toegewezen aan de lokale variabele t
en doorgegeven aan Method3
. Op dat moment ziet het dat de parameter type
vereist PublicMethods
, maar er zijn geen vereisten voor T
en produceert een nieuwe waarschuwing. Om dit op te lossen, moeten we aantekeningen maken en doorgeven door aantekeningen helemaal in de aanroepketen toe te passen totdat we een statisch bekend type (zoals System.DateTime
of System.Tuple
) of een andere geannoteerde waarde bereiken. In dit geval moeten we aantekeningen maken op de typeparameter T
van 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();
...
}
Er zijn nu geen waarschuwingen omdat de trimmer weet welke leden kunnen worden geopend via runtime-reflectie (openbare methoden) en op welke typen (System.DateTime
) en deze behouden blijven. Het is raadzaam aantekeningen toe te voegen, zodat de trimmer weet wat u moet behouden.
Waarschuwingen die door deze extra vereisten worden geproduceerd, worden automatisch onderdrukt als de betreffende code zich in een methode bevindt met RequiresUnreferencedCode
.
In tegenstelling tot RequiresUnreferencedCode
, wat simpelweg de incompatibiliteit rapporteert, maakt het toevoegen DynamicallyAccessedMembers
van de code compatibel met bijsnijden.
Notitie
Als u DynamicallyAccessedMembersAttribute
deze gebruikt, worden alle opgegeven DynamicallyAccessedMemberTypes
leden van het type root. Dit betekent dat de leden en alle metagegevens waarnaar wordt verwezen door die leden behouden blijven. Dit kan leiden tot veel grotere apps dan verwacht. Wees voorzichtig met het gebruik van het minimum DynamicallyAccessedMemberTypes
dat vereist is.
Trimmerwaarschuwingen onderdrukken
Als u op een of andere manier kunt bepalen dat de aanroep veilig is en alle benodigde code niet wordt afgekapt, kunt u de waarschuwing ook onderdrukken met behulp van UnconditionalSuppressMessageAttribute. Voorbeeld:
[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();
}
Waarschuwing
Wees erg voorzichtig bij het onderdrukken van trimwaarschuwingen. Het is mogelijk dat de aanroep nu compatibel is met trim, maar als u de code wijzigt die kan worden gewijzigd en u vergeet mogelijk alle onderdrukkingen te controleren.
UnconditionalSuppressMessage
is net, SuppressMessage
maar het kan worden gezien door publish
en andere hulpprogramma's na de build.
Belangrijk
Gebruik of #pragma warning disable
om trimmerwaarschuwingen niet SuppressMessage
te onderdrukken. Deze werken alleen voor de compiler, maar blijven niet behouden in de gecompileerde assembly. Trimmer werkt op gecompileerde assembly's en zou de onderdrukking niet zien.
De onderdrukking is van toepassing op het gehele lichaam van de methode. Dus in ons voorbeeld hierboven worden alle IL2026
waarschuwingen van de methode onderdrukt. Dit maakt het moeilijker om te begrijpen, omdat het niet duidelijk is welke methode de problematische methode is, tenzij u een opmerking toevoegt. Belangrijker nog, als de code in de toekomst verandert, bijvoorbeeld als ReportResults
de code ook incompatibel wordt, wordt er geen waarschuwing gerapporteerd voor deze methode-aanroep.
U kunt dit oplossen door de problematische methode te herstructureren in een afzonderlijke methode of lokale functie en vervolgens de onderdrukking toe te passen op alleen die methode:
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
}
}