.NET-bibliotheken voorbereiden voor bijsnijden
De .NET SDK maakt het mogelijk om de grootte van zelfstandige apps te verkleinen door het bijsnijden. Door het bijsnijden worden ongebruikte code uit de app en de bijbehorende afhankelijkheden verwijderd. Niet alle code is compatibel met bijsnijden. .NET biedt trimanalysewaarschuwingen voor het detecteren van patronen die mogelijk bijgesneden apps breken. Dit artikel:
- Hierin wordt beschreven hoe u bibliotheken voorbereidt voor bijsnijden.
- Biedt aanbevelingen voor het oplossen van veelvoorkomende knipwaarschuwingen.
Vereisten
.NET 8 SDK of hoger.
Waarschuwingen voor het bijsnijden van bibliotheken inschakelen
Waarschuwingen bijsnijden in een bibliotheek vindt u met een van de volgende methoden:
- Projectspecifieke trimming inschakelen met behulp van de
IsTrimmable
eigenschap. - Het maken van een test-app voor bijsnijden die gebruikmaakt van de bibliotheek en het inschakelen van bijsnijden voor de test-app. Het is niet nodig om naar alle API's in de bibliotheek te verwijzen.
We raden u aan beide benaderingen te gebruiken. Projectspecifieke bijsnijding is handig en toont trimwaarschuwingen voor één project, maar is afhankelijk van de verwijzingen die zijn gemarkeerd als trim-compatibel om alle waarschuwingen te zien. Het bijsnijden van een test-app werkt meer, maar toont alle waarschuwingen.
Projectspecifieke bijsnijding inschakelen
Instellen <IsTrimmable>true</IsTrimmable>
in het projectbestand.
<PropertyGroup>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
Als u de eigenschap IsTrimmable
MSBuild instelt om de assembly als 'trimmable' te true
markeren en knipwaarschuwingen mogelijk te maken. "Trimmable": het project:
- Wordt beschouwd als compatibel met bijsnijden.
- Er mogen geen trimgerelateerde waarschuwingen worden gegenereerd bij het bouwen. Wanneer de assembly wordt gebruikt in een bijgesneden app, worden de ongebruikte leden in de uiteindelijke uitvoer ingekort.
De IsTrimmable
eigenschap wordt standaard ingesteld true
bij het configureren van een project als AOT-compatibel met <IsAotCompatible>true</IsAotCompatible>
. Zie AOT-compatibiliteitsanalyses voor meer informatie.
Als u trimwaarschuwingen wilt genereren zonder het project als trim-compatibel te markeren, gebruikt <EnableTrimAnalyzer>true</EnableTrimAnalyzer>
u in plaats <IsTrimmable>true</IsTrimmable>
van .
Alle waarschuwingen met test-app weergeven
Als u alle analysewaarschuwingen voor een bibliotheek wilt weergeven, moet de trimmer de implementatie van de bibliotheek en alle afhankelijkheden analyseren die door de bibliotheek worden gebruikt.
Bij het bouwen en publiceren van een bibliotheek:
- De implementaties van de afhankelijkheden zijn niet beschikbaar.
- De beschikbare referentieassembly's hebben onvoldoende informatie voor de trimmer om te bepalen of ze compatibel zijn met bijsnijden.
Vanwege de afhankelijkheidsbeperkingen moet er een zelfstandige test-app worden gemaakt die gebruikmaakt van de bibliotheek en de bijbehorende afhankelijkheden. De test-app bevat alle informatie die de trimmer nodig heeft om een waarschuwing te geven over incompatibiliteit bij het bijsnijden:
- De bibliotheekcode.
- De code waarnaar de bibliotheek verwijst vanuit de afhankelijkheden.
Notitie
Als de bibliotheek ander gedrag heeft, afhankelijk van het doelframework, maakt u een bekorte test-app voor elk van de doelframeworks die ondersteuning bieden voor bijsnijden. Als de bibliotheek bijvoorbeeld gebruikmaakt van voorwaardelijke compilatie , bijvoorbeeld #if NET7_0
om het gedrag te wijzigen.
De test-app voor bijsnijden maken:
- Maak een afzonderlijk consoletoepassingsproject.
- Voeg een verwijzing naar de bibliotheek toe.
- Wijzig het project dat lijkt op het project dat hieronder wordt weergegeven met behulp van de volgende lijst:
Als de bibliotheek is gericht op een TFM die bijvoorbeeld net472
niet kan worden ingekort, is netstandard2.0
er geen voordeel bij het maken van een test-app voor bijsnijden. Bijsnijden wordt alleen ondersteund voor .NET 6 en hoger.
- Toevoegen
<PublishTrimmed>true</PublishTrimmed>
. - Voeg een verwijzing toe naar het bibliotheekproject met
<ProjectReference Include="/Path/To/YourLibrary.csproj" />
. - Geef de bibliotheek op als een hoofdassembly
<TrimmerRootAssembly Include="YourLibraryName" />
met een trimmer.TrimmerRootAssembly
zorgt ervoor dat elk deel van de bibliotheek wordt geanalyseerd. Het vertelt de trimmer dat deze assembly een "root" is. Een 'root'-assembly betekent dat de trimmer elke aanroep in de bibliotheek analyseert en alle codepaden doorkruist die afkomstig zijn van die assembly.
.csproj-bestand
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
<TrimmerRootAssembly Include="MyLibrary" />
</ItemGroup>
</Project>
Zodra het projectbestand is bijgewerkt, voert dotnet publish
u de doelruntime-id (RID) uit.
dotnet publish -c Release -r <RID>
Volg het voorgaande patroon voor meerdere bibliotheken. Als u waarschuwingen voor trimanalyse voor meer dan één bibliotheek tegelijk wilt zien, voegt u ze allemaal toe aan hetzelfde project als ProjectReference
items TrimmerRootAssembly
. Als een van de hoofdbibliotheken een trim-onvriendelijk API in een afhankelijkheid gebruikt, worden alle bibliotheken aan hetzelfde project toegevoegd en ProjectReference
items worden gewaarschuwd voor afhankelijkheden.TrimmerRootAssembly
Als u waarschuwingen wilt zien die alleen met een bepaalde bibliotheek te maken hebben, verwijst u alleen naar die bibliotheek.
Notitie
De analyseresultaten zijn afhankelijk van de implementatiedetails van de afhankelijkheden. Bijwerken naar een nieuwe versie van een afhankelijkheid kan leiden tot analysewaarschuwingen:
- Als de nieuwe versie niet-begrepen reflectiepatronen heeft toegevoegd.
- Zelfs als er geen API-wijzigingen waren.
- Het introduceren van trimanalysewaarschuwingen is een belangrijke wijziging wanneer de bibliotheek wordt gebruikt met
PublishTrimmed
.
Trimwaarschuwingen oplossen
In de voorgaande stappen worden waarschuwingen over code geproduceerd die problemen kunnen veroorzaken bij gebruik in een bijgesneden app. In de volgende voorbeelden ziet u de meest voorkomende waarschuwingen met aanbevelingen voor het oplossen van deze waarschuwingen.
RequiresUnreferencedCode
Houd rekening met de volgende code die wordt gebruikt [RequiresUnreferencedCode]
om aan te geven dat voor de opgegeven methode dynamische toegang tot code is vereist die niet statisch wordt genoemd, bijvoorbeeld via System.Reflection.
public class MyLibrary
{
public static void MyMethod()
{
// warning IL2026 :
// MyLibrary.MyMethod: Using 'MyLibrary.DynamicBehavior'
// which has [RequiresUnreferencedCode] can break functionality
// when trimming app code.
DynamicBehavior();
}
[RequiresUnreferencedCode(
"DynamicBehavior is incompatible with trimming.")]
static void DynamicBehavior()
{
}
}
De voorgaande gemarkeerde code geeft aan dat de bibliotheek een methode aanroept die expliciet is geannoteerd als niet compatibel met bijsnijden. Als u de waarschuwing wilt verwijderen, moet u overwegen of MyMethod
u moet bellen DynamicBehavior
. Zo ja, dan kunt u aantekeningen toevoegen aan de beller MyMethod
waarmee [RequiresUnreferencedCode]
de waarschuwing wordt doorgegeven, zodat bellers van MyMethod
een waarschuwing in plaats daarvan een waarschuwing krijgen:
public class MyLibrary
{
[RequiresUnreferencedCode("Calls DynamicBehavior.")]
public static void MyMethod()
{
DynamicBehavior();
}
[RequiresUnreferencedCode(
"DynamicBehavior is incompatible with trimming.")]
static void DynamicBehavior()
{
}
}
Zodra u het kenmerk helemaal hebt doorgegeven aan de openbare API, worden apps aangeroepen die de bibliotheek aanroepen:
- Ontvang alleen waarschuwingen voor openbare methoden die niet kunnen worden ingekort.
- Ontvang geen waarschuwingen zoals
IL2104: Assembly 'MyLibrary' produced trim warnings
.
DynamischAccessedMembers
public class MyLibrary3
{
static void UseMethods(Type type)
{
// warning IL2070: MyLibrary.UseMethods(Type): 'this' argument does not satisfy
// 'DynamicallyAccessedMemberTypes.PublicMethods' in call to
// 'System.Type.GetMethods()'.
// The parameter 't' of method 'MyLibrary.UseMethods(Type)' doesn't have
// matching annotations.
foreach (var method in type.GetMethods())
{
// ...
}
}
}
In de voorgaande code UseMethods
wordt een reflectiemethode aangeroepen die een [DynamicallyAccessedMembers]
vereiste heeft. De vereiste geeft aan dat de openbare methoden van het type beschikbaar zijn. Voldoen aan de vereiste door dezelfde vereiste toe te voegen aan de parameter van UseMethods
.
static void UseMethods(
// State the requirement in the UseMethods parameter.
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
// ...
}
Nu worden aanroepen om waarschuwingen te UseMethods
produceren als ze waarden doorgeven die niet aan de PublicMethods vereiste voldoen. [RequiresUnreferencedCode]
Zodra u dergelijke waarschuwingen hebt doorgegeven aan openbare API's, bent u klaar.
In het volgende voorbeeld loopt een onbekend type over naar de parameter van de geannoteerde methode. Het onbekende Type
is afkomstig van een veld:
static Type type;
static void UseMethodsHelper()
{
// warning IL2077: MyLibrary.UseMethodsHelper(Type): 'type' argument does not satisfy
// 'DynamicallyAccessedMemberTypes.PublicMethods' in call to
// 'MyLibrary.UseMethods(Type)'.
// The field 'System.Type MyLibrary::type' does not have matching annotations.
UseMethods(type);
}
Op dezelfde manier is het probleem dat het veld type
wordt doorgegeven aan een parameter met deze vereisten. Dit probleem is opgelost door het veld toe te voegen [DynamicallyAccessedMembers]
. [DynamicallyAccessedMembers]
waarschuwt voor code die incompatibele waarden aan het veld toewijst. Soms wordt dit proces voortgezet totdat een openbare API wordt geannoteerd, en andere keren eindigt het wanneer een concreet type naar een locatie stroomt met deze vereisten. Voorbeeld:
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
static Type type;
static void UseMethodsHelper()
{
MyLibrary.type = typeof(System.Tuple);
}
In dit geval houdt de trimanalyse openbare methoden van Tupleen produceert verdere waarschuwingen.
Aanbevelingen
- Vermijd reflectie indien mogelijk. Wanneer u weerspiegeling gebruikt, minimaliseert u het reflectiebereik zodat deze alleen bereikbaar is vanuit een klein deel van de bibliotheek.
- Aantekeningen toevoegen aan code om
DynamicallyAccessedMembers
de bijsnijdvereisten statisch uit te drukken, indien mogelijk. - Overweeg om code te herorganiseren zodat deze een analyseerbaar patroon volgt dat kan worden geannoteerd met
DynamicallyAccessedMembers
- Wanneer code niet compatibel is met bijsnijden, maakt u er aantekeningen
RequiresUnreferencedCode
bij en geeft u deze aanroepers door totdat de relevante openbare API's zijn geannoteerd. - Vermijd het gebruik van code die gebruikmaakt van weerspiegeling op een manier die niet wordt begrepen door de statische analyse. Reflectie in statische constructors moet bijvoorbeeld worden vermeden. Het gebruik van statisch onanalyse reflectie in statische constructors resulteert in de waarschuwing die wordt doorgegeven aan alle leden van de klasse.
- Vermijd aantekeningen toevoegen aan virtuele methoden of interfacemethoden. Bij het toevoegen van aantekeningen aan virtuele methoden of interfacemethoden moeten alle overschrijvingen overeenkomende aantekeningen bevatten.
- Als een API voornamelijk incompatibel is, moeten alternatieve coderingsmethoden voor de API mogelijk worden overwogen. Een veelvoorkomend voorbeeld is serializers op basis van weerspiegeling. In dergelijke gevallen kunt u overwegen om andere technologie, zoals brongeneratoren, te gebruiken om code te produceren die gemakkelijker statisch wordt geanalyseerd. Zie Bijvoorbeeld Het gebruik van brongeneratie in System.Text.Json
Waarschuwingen voor niet-analyseerbare patronen oplossen
Het is beter om waarschuwingen op te lossen door de intentie van uw code uit te drukken met behulp [RequiresUnreferencedCode]
van en DynamicallyAccessedMembers
indien mogelijk. In sommige gevallen bent u echter geïnteresseerd in het inschakelen van het bijsnijden van een bibliotheek die gebruikmaakt van patronen die niet met deze kenmerken kunnen worden uitgedrukt of zonder bestaande code te herstructureren. In deze sectie worden enkele geavanceerde manieren beschreven om waarschuwingen voor trimanalyse op te lossen.
Waarschuwing
Deze technieken kunnen het gedrag of uw code wijzigen of leiden tot runtime-uitzonderingen als deze onjuist worden gebruikt.
VoorwaardelijkeSuppressMessage
Houd rekening met code die:
- De intentie kan niet worden uitgedrukt met de aantekeningen.
- Genereert een waarschuwing, maar vertegenwoordigt geen echt probleem tijdens runtime.
De waarschuwingen kunnen worden onderdrukt door UnconditionalSuppressMessageAttribute. Dit is vergelijkbaar met SuppressMessageAttribute
, maar het wordt bewaard in IL en gerespecteerd tijdens het analyseren van de trim.
Waarschuwing
Bij het onderdrukken van waarschuwingen bent u verantwoordelijk voor het garanderen van de trimcompatibiliteit van de code op basis van invarianten waarvan u weet dat ze waar zijn door inspectie en testen. Wees voorzichtig met deze aantekeningen, omdat als deze onjuist zijn of als er invarianten van uw codewijziging zijn, ze mogelijk onjuiste code verbergen.
Voorbeeld:
class TypeCollection
{
Type[] types;
// Ensure that only types with preserved constructors are stored in the array
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
public Type this[int i]
{
// warning IL2063: TypeCollection.Item.get: Value returned from method
// 'TypeCollection.Item.get' can't be statically determined and may not meet
// 'DynamicallyAccessedMembersAttribute' requirements.
get => types[i];
set => types[i] = value;
}
}
class TypeCreator
{
TypeCollection types;
public void CreateType(int i)
{
types[i] = typeof(TypeWithConstructor);
Activator.CreateInstance(types[i]); // No warning!
}
}
class TypeWithConstructor
{
}
In de voorgaande code is de eigenschap indexeerfunctie geannoteerd, zodat de geretourneerde Type
code voldoet aan de vereisten van CreateInstance
. Dit zorgt ervoor dat de TypeWithConstructor
constructor wordt bewaard en dat de aanroep om CreateInstance
niet te waarschuwen. De aantekening van de indexeerfunctieset zorgt ervoor dat alle typen die zijn opgeslagen in de Type[]
constructor. De analyse kan dit echter niet zien en produceert een waarschuwing voor de getter, omdat het niet weet dat het geretourneerde type de constructor heeft behouden.
Als u zeker weet dat aan de vereisten wordt voldaan, kunt u deze waarschuwing dempen door deze waarschuwing toe te voegen [UnconditionalSuppressMessage]
aan de getter:
class TypeCollection
{
Type[] types;
// Ensure that only types with preserved constructors are stored in the array
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
public Type this[int i]
{
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2063",
Justification = "The list only contains types stored through the annotated setter.")]
get => types[i];
set => types[i] = value;
}
}
class TypeCreator
{
TypeCollection types;
public void CreateType(int i)
{
types[i] = typeof(TypeWithConstructor);
Activator.CreateInstance(types[i]); // No warning!
}
}
class TypeWithConstructor
{
}
Het is belangrijk om te onderstrepen dat het alleen geldig is om een waarschuwing te onderdrukken als er aantekeningen of code zijn die ervoor zorgen dat de weerspiegelde leden zichtbare doelen van reflectie zijn. Het is niet voldoende dat het lid een doel was van een aanroep, veld of eigenschapstoegang. Dit lijkt soms het geval te zijn, maar dergelijke code is uiteindelijk gebonden om te breken, omdat er meer optimalisaties voor bijsnijden worden toegevoegd. Eigenschappen, velden en methoden die geen zichtbare doelen van weerspiegeling zijn, kunnen inline worden geplaatst, hun namen laten verwijderen, naar verschillende typen worden verplaatst of anderszins worden geoptimaliseerd op manieren die de reflectie op deze objecten verbreken. Wanneer u een waarschuwing onderdrukt, is het alleen toegestaan om te weerspiegelen op doelen die zichtbare doelen van reflectie waren voor de trimming analyzer elders.
// Invalid justification and suppression: property being non-reflectively
// used by the app doesn't guarantee that the property will be available
// for reflection. Properties that are not visible targets of reflection
// are already optimized away with Native AOT trimming and may be
// optimized away for non-native deployment in the future as well.
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2063",
Justification = "*INVALID* Only need to serialize properties that are used by"
+ "the app. *INVALID*")]
public string Serialize(object o)
{
StringBuilder sb = new StringBuilder();
foreach (var property in o.GetType().GetProperties())
{
AppendProperty(sb, property, o);
}
return sb.ToString();
}
DynamicDependency
Het [DynamicDependency]
kenmerk kan worden gebruikt om aan te geven dat een lid een dynamische afhankelijkheid heeft van andere leden. Dit resulteert in het bewaren van de leden waarnaar wordt verwezen wanneer het lid met het kenmerk wordt bewaard, maar niet zelf waarschuwingen dempt. In tegenstelling tot de andere kenmerken, die de trimanalyse informeren over het weerspiegelingsgedrag van de code, [DynamicDependency]
blijven alleen andere leden behouden. Dit kan samen worden gebruikt om [UnconditionalSuppressMessage]
enkele analysewaarschuwingen op te lossen.
Waarschuwing
Gebruik [DynamicDependency]
kenmerk alleen als laatste redmiddel wanneer de andere benaderingen niet haalbaar zijn. Het verdient de voorkeur om het weerspiegelingsgedrag uit te drukken met of [RequiresUnreferencedCode]
[DynamicallyAccessedMembers]
.
[DynamicDependency("Helper", "MyType", "MyAssembly")]
static void RunHelper()
{
var helper = Assembly.Load("MyAssembly").GetType("MyType").GetMethod("Helper");
helper.Invoke(null, null);
}
ZonderDynamicDependency
, kan bijsnijden volledig worden MyAssembly
verwijderd Helper
of verwijderd MyAssembly
als er ergens anders niet naar wordt verwezen, waardoor een waarschuwing wordt weergegeven die een mogelijke fout aangeeft tijdens de uitvoering. Het kenmerk zorgt ervoor dat dit Helper
behouden blijft.
Het kenmerk geeft de leden op die moeten worden bewaard via een string
of via DynamicallyAccessedMemberTypes
. Het type en de assembly zijn impliciet in de kenmerkcontext, of expliciet opgegeven in het kenmerk (op Type
, of op string
s voor het type en de assemblynaam).
Het type en de lidtekenreeksen gebruiken een variatie van de tekenreeksindeling voor de opmerkings-id van de C#-documentatie, zonder het lidvoorvoegsel. De lidtekenreeks mag niet de naam van het declaratietype bevatten en kan parameters weglaten om alle leden van de opgegeven naam te behouden. Enkele voorbeelden van de indeling worden weergegeven in de volgende code:
[DynamicDependency("MyMethod()")]
[DynamicDependency("MyMethod(System,Boolean,System.String)")]
[DynamicDependency("MethodOnDifferentType()", typeof(ContainingType))]
[DynamicDependency("MemberName")]
[DynamicDependency("MemberOnUnreferencedAssembly", "ContainingType"
, "UnreferencedAssembly")]
[DynamicDependency("MemberName", "Namespace.ContainingType.NestedType", "Assembly")]
// generics
[DynamicDependency("GenericMethodName``1")]
[DynamicDependency("GenericMethod``2(``0,``1)")]
[DynamicDependency(
"MethodWithGenericParameterTypes(System.Collections.Generic.List{System.String})")]
[DynamicDependency("MethodOnGenericType(`0)", "GenericType`1", "UnreferencedAssembly")]
[DynamicDependency("MethodOnGenericType(`0)", typeof(GenericType<>))]
Het [DynamicDependency]
kenmerk is ontworpen om te worden gebruikt in gevallen waarin een methode weerspiegelingspatronen bevat die zelfs niet kunnen worden geanalyseerd met behulp van DynamicallyAccessedMembersAttribute
.