Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Sada .NET SDK umožňuje pomocí ořezávání zmenšit velikost samostatných aplikací. Oříznutí odebere z aplikace nepoužitý kód a jeho závislosti. Ne všechen kód je kompatibilní s trimmingem. .NET poskytuje upozornění analýzy trimmování pro zjišťování vzorů, které by mohly narušit trimmované aplikace. Tento článek:
- Popisuje, jak připravit knihovny na oříznutí.
- Poskytuje doporučení pro řešení běžných problémů s upozorněními o oříznutí.
Požadavky
.NET 8 SDK nebo novější
Zapnout upozornění na zkracování knihovny
Upozornění oříznutí v knihovně najdete pomocí některé z následujících metod:
- Povolení oříznutí specifického pro projekt pomocí vlastnosti
IsTrimmable. - Vytvoření testovací aplikace pro stříhání, která používá knihovnu, a povolení stříhání pro testovací aplikaci. Není nutné odkazovat na všechna rozhraní API v knihovně.
Doporučujeme používat oba přístupy. Oříznutí specifické pro projekt je pohodlné a zobrazuje upozornění oříznutí pro jeden projekt, ale spoléhá na odkazy, které jsou označené jako kompatibilní s oříznutím, aby se zobrazila všechna upozornění. Oříznutí testovací aplikace je více práce, ale ukazuje všechna upozornění.
Povolit oříznutí specifické pro projekt
Nastavte <IsTrimmable>true</IsTrimmable> v souboru projektu.
<PropertyGroup>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
Nastavením vlastnosti IsTrimmable v MSBuild na true se sestavení označí jako "oříznutelné" a povolí se upozornění na oříznutí. "Trimmable" znamená projekt:
- Považuje se za slučitelné s oříznutím.
- Při vytváření by neměla být generována upozornění související s trimováním. Při použití v optimalizované aplikaci má sestavení nepoužívané členy odstraněné v konečném výstupu.
Výchozí hodnota vlastnosti IsTrimmable je true při konfiguraci projektu jako kompatibilního s AOT pomocí <IsAotCompatible>true</IsAotCompatible>. Další informace najdete v tématu Analyzátory kompatibility AOT.
Chcete-li generovat upozornění na ořezání, aniž by byl projekt označen jako kompatibilní s ořezem, použijte <EnableTrimAnalyzer>true</EnableTrimAnalyzer> místo <IsTrimmable>true</IsTrimmable>.
Ověřte, že odkazovaná sestavení jsou kompatibilní s optimalizací ořezání.
Pokud povolíte trimovací analýzu pro knihovnu, můžete volitelně povolit ověření, že všechna odkazovaná sestavení jsou také opatřena poznámkami pro kompatibilitu s trimováním nastavením vlastnosti na VerifyReferenceTrimCompatibility.
<PropertyGroup>
<IsTrimmable>true</IsTrimmable>
<VerifyReferenceTrimCompatibility>true</VerifyReferenceTrimCompatibility>
</PropertyGroup>
Pokud je tato vlastnost povolená, analyzátor upozorní na všechna odkazovaná sestavení, která nemají IsTrimmable metadata. Pomáhá zajistit, aby všechny závislosti v projektu byly označeny pro kompatibilitu ořezávání. Upozornění, které se vygeneruje, je IL2125.
Toto ověření je dobrovolné přihlášení, protože:
- Ne všechny knihovny kompatibilní s trimem byly aktualizovány tak, aby zahrnovaly metadata
IsTrimmable. - Upozornění může být zahlcující, pokud máte mnoho závislostí, které správně fungují s redukcí, ale nejsou explicitně označeny jako takové.
Zvážit zapnutí tohoto ověření, pokud chcete zajistit, aby všechny vaše závislosti byly explicitně označeny autory jako trim-kompatibilní.
Zobrazení všech upozornění pomocí testovací aplikace
Aby se zobrazila všechna upozornění analýzy pro knihovnu, musí trimmer analyzovat implementaci knihovny a všech závislostí, které knihovna používá.
Při sestavování a publikování knihovny:
- Implementace závislostí nejsou k dispozici.
- Dostupná referenční sestavení nemají dostatek informací, které by trimru umožnily určit, zda jsou kompatibilní s procesem ořezávání.
Kvůli omezením závislostí se musí vytvořit samostatná testovací aplikace, která používá knihovnu a její závislosti. Testovací aplikace obsahuje všechny informace, které trimmer vyžaduje k vydání upozornění na nekompatibility ořezání v:
- Kód knihovny.
- Kód, na který knihovna odkazuje ze závislostí.
Poznámka:
Pokud má knihovna jiné chování v závislosti na cílovém rozhraní, vytvořte testovací aplikaci pro oříznutí pro každou z cílových architektur, která podporuje oříznutí. Pokud například knihovna používá podmíněnou kompilaci , například #if NET7_0 ke změně chování.
Pro vytvoření testovací aplikace pro oříznutí:
- Vytvořte samostatný projekt konzolové aplikace.
- Přidejte odkaz na knihovnu.
- Upravte projekt podobný následujícímu projektu pomocí následujícího seznamu:
Pokud knihovna cílí na TFM, který nelze oříznout, například net472 nebo netstandard2.0, není přínosné vytvářet testovací aplikaci pro oříznutí. Trimmování je podporováno pouze pro .NET 6 a novější.
- Přidat
<PublishTrimmed>true</PublishTrimmed>. - Přidejte odkaz na projekt knihovny pomocí
<ProjectReference Include="/Path/To/YourLibrary.csproj" />. - Zadejte knihovnu jako základní sestavení pomocí
<TrimmerRootAssembly Include="YourLibraryName" />.-
TrimmerRootAssemblyzajišťuje, aby byla analyzována každá část knihovny. Říká trimmeru, že toto assembly je "root". Sestavení "root" znamená, že zatřižovač analyzuje každé volání v knihovně a prochází všechny cesty kódu, které pocházejí z daného sestavení.
-
Soubor .csproj
<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>
Po aktualizaci souboru projektu spusťte dotnet publish s cílovým identifikátorem modulu runtime (RID).
dotnet publish -c Release -r <RID>
Postupujte podle předchozího vzoru pro více knihoven. Pokud chcete zobrazit upozornění z analýzy trimování pro více knihoven najednou, přidejte je všechny do stejného projektu jako položky ProjectReference a TrimmerRootAssembly. Přidání všech knihoven a položek ProjectReferenceTrimmerRootAssembly do stejného projektu vydá upozornění na závislosti, pokud některá z kořenových knihoven používá ve své závislosti API nevhodné pro ořezávání. Abyste zobrazili upozornění, která se týkají pouze konkrétní knihovny, uveďte pouze tuto knihovnu.
Poznámka:
Výsledky analýzy závisí na podrobnostech implementace závislostí. Aktualizace na novou verzi závislosti může vést k varováním vzniklým při analýze.
- Jestliže nová verze přidala nesrozumitelné reflexní vzory.
- I když nedošlo k žádným změnám rozhraní API.
- Představení upozornění pro analýzu oříznutí je zásadní změnou při použití knihovny s
PublishTrimmed.
Požadavky na cílovou architekturu
Při přípravě knihoven na oříznutí se zaměřte na nejnovější podporované TFM. To vám pomůže využívat nejnovější vylepšení analyzátoru. Zaměřte se minimálně na net6.0 nebo novější. Tato verze je vyžadována pro upozornění na analýzu oříznutí.
Pokud vaše knihovna cílí také na frameworky starší než net6.0 (například netstandard2.0 nebo net472), pro více cílů zahrňte net6.0. Tím zajistíte, že aplikace cílící na net6.0 nebo novější, získají verzi knihovny, která podporuje trim analýzu.
Použijte funkci MSBuild IsTargetFrameworkCompatible k podmíněnému povolení IsTrimmable pro net6.0 a novější:
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0;net10.0</TargetFrameworks>
<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))">true</IsTrimmable>
</PropertyGroup>
Další informace naleznete v tématu Oříznutí nelze použít s rozhraním .NET Standard nebo .NET Framework.
Odstranit upozornění na oříznutí
Předchozí kroky generují upozornění na kód, který může způsobovat problémy při použití v oříznuté aplikaci. Následující příklady ukazují nejběžnější upozornění s doporučeními pro jejich opravu.
VyžadujeNepřiřazenýKód
Vezměte v úvahu následující kód, který používá [RequiresUnreferencedCode] k označení, že zadaná metoda vyžaduje dynamický přístup k kódu, který není odkazován staticky, například prostřednictvím 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()
{
}
}
Předchozí zvýrazněný kód ukazuje, že knihovna volá metodu, která byla explicitně označena jako nekompatibilní s redukcí. Pro zrušení upozornění zvažte, zda je nutné, aby MyMethod volal DynamicBehavior. Pokud ano, označte volajícího MyMethod pomocí [RequiresUnreferencedCode], což rozšíří upozornění tak, aby volající MyMethod dostali místo toho varování.
public class MyLibrary
{
[RequiresUnreferencedCode("Calls DynamicBehavior.")]
public static void MyMethod()
{
DynamicBehavior();
}
[RequiresUnreferencedCode(
"DynamicBehavior is incompatible with trimming.")]
static void DynamicBehavior()
{
}
}
Po rozšíření atributu až do veřejného rozhraní API aplikace volají knihovnu:
- Zobrazí se upozornění jenom pro veřejné metody, které nejsou optimalizovatelné.
- Vyvarujte se upozornění jako
IL2104: Assembly 'MyLibrary' produced trim warnings.
DynamicallyAccessedMembers
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())
{
// ...
}
}
}
V předchozím kódu UseMethods volá reflexní metodu, která má [DynamicallyAccessedMembers] požadavek. Požadavek uvádí, že veřejné metody tohoto typu jsou k dispozici. Splnění požadavku přidáním stejného požadavku na parametr .UseMethods
static void UseMethods(
// State the requirement in the UseMethods parameter.
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
// ...
}
Nyní veškerá volání UseMethods způsobí upozornění, pokud předají hodnoty, které nevyhovují požadavku PublicMethods.
[RequiresUnreferencedCode] Podobně jako v případě, jakmile rozšíříte tato upozornění do veřejných rozhraní API, máte hotovo.
V následujícím příkladu neznámý typ proudí do anotovaného parametru metody. Neznámé Type je z pole:
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);
}
Podobně zde problém spočívá v tom, že pole type se předává do parametru s těmito požadavky. Opravili jsme ho přidáním [DynamicallyAccessedMembers] do pole.
[DynamicallyAccessedMembers] varuje o kódu, který přiřadí nekompatibilní hodnoty k poli. Někdy tento proces pokračuje, dokud nebude veřejné rozhraní API opatřeno poznámkami, a jindy skončí, když konkrétní typ přejde do umístění s těmito požadavky. Příklad:
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
static Type type;
static void UseMethodsHelper()
{
MyLibrary.type = typeof(System.Tuple);
}
V tomto případě analýza oříznutí uchovává veřejné metody Tuplea vytváří další upozornění.
Doporučení
- Pokud je to možné, vyhněte se reflexi. Při použití reflexe minimalizujte rozsah reflexe tak, aby byl dostupný jenom z malé části knihovny.
- Označte kód pomocí
DynamicallyAccessedMembersk tomu, abyste staticky specifikovali požadavky na oříznutí, pokud je to možné. - Zvažte přeuspořádání kódu, aby postupoval podle analyzovatelného vzoru, který může být opatřen poznámkami
DynamicallyAccessedMembers - Pokud kód není kompatibilní s oříznutím, označte ho poznámkou
RequiresUnreferencedCodea tuto poznámku předejte volajícím, dokud nebudou příslušná veřejná rozhraní API také opatřena poznámkou. - Nepoužívejte kód, který používá reflexi způsobem, kterému statická analýza nerozumí. Například bychom se měli vyhnout reflexi ve statických konstruktorech. Použití staticky neanalyzovatelné reflexe ve statických konstruktorech vede k šíření upozornění na všechny členy třídy.
- Vyhněte se přidávání poznámek k virtuálním metodám nebo metodám rozhraní. Přidávání poznámek k virtuálním nebo rozhraní metodám vyžaduje, aby všechna přepsání obsahovala odpovídající poznámky.
- Pokud je rozhraní API většinou trim nekompatibilní, možná bude potřeba zvážit alternativní přístupy ke kódování rozhraní API. Běžným příkladem jsou serializátory založené na reflexi. V těchto případech zvažte přijetí dalších technologií, jako jsou generátory zdrojů, k vytvoření kódu, který je snadněji staticky analyzován. Podívejte se například na Jak používat generování zdrojového kódu vSystem.Text.Json .
Řešení upozornění pro ne analyzovatelné vzory
Je lepší řešit upozornění vyjádřením záměru vašeho kódu pomocí [RequiresUnreferencedCode] a DynamicallyAccessedMembers, pokud je to možné. V některých případech vás ale může zajímat povolení oříznutí knihovny, která používá vzory, které nelze vyjádřit pomocí těchto atributů, nebo bez refaktoringu existujícího kódu. Tato část popisuje některé pokročilé metody řešení upozornění při analýze úprav.
Upozornění
Tyto techniky mohou změnit chování vašeho kódu nebo vést k chybovým výjimkám během běhu programu, pokud jsou použity nesprávně.
Potlačení zprávy bez podmínek
Vezměte v úvahu kód, který:
- Záměr nelze vyjádřit pomocí poznámek.
- Vygeneruje upozornění, ale nepředstavuje skutečný problém za běhu.
Upozornění lze potlačit pomocí UnconditionalSuppressMessageAttribute. To se podobá SuppressMessageAttribute, ale je uchováváno v IL a je respektováno během analýzy oříznutí.
Upozornění
Při potlačování upozornění jste zodpovědní za zajištění kompatibility prořezávání kódu na základě invariantů, které znáte jako pravdivé na základě kontroly a testování. U těchto poznámek buďte opatrní, protože pokud jsou nesprávné nebo pokud se invarianty vašeho kódu změní, můžou nakonec skrýt nesprávný kód.
Příklad:
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
{
}
V předchozím kódu byla vlastnost indexeru anotována tak, aby vrácená Type hodnota splňovala požadavky .CreateInstance Tím se zajistí, že konstruktor TypeWithConstructor je zachován a že při volání CreateInstance nedochází k varování. Anotace setteru indexeru zajišťuje, že všechny typy uložené v Type[] mají konstruktor. Analýza ale tuto možnost nevidí a vygeneruje upozornění pro getter, protože neví, že vrácený typ má svůj konstruktor zachován.
Pokud jste si jistí, že požadavky jsou splněny, můžete toto upozornění ztlumit přidáním [UnconditionalSuppressMessage] do getteru.
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
{
}
Je důležité podtrhnout, že je platné potlačit upozornění pouze v případě, že existují poznámky nebo kód, které zajistí, aby odrazové členy byly viditelné cíle reflexe. Nestačí, aby byl člen cílem přístupu k volání, poli nebo vlastnosti. Někdy se může zdát, že to tak je, ale takový kód může nakonec selhat, jakmile přibudou další optimalizace. Vlastnosti, pole a metody, které nejsou viditelnými cíli reflexe, mohou být vloženy, jejich názvy mohou být odebrány, mohou být přesunuty do jiných typů nebo jinak optimalizovány způsobem, který znemožňuje jejich odraz. Když potlačíte upozornění, je možné odrážet pouze cíle, které byly viditelné cíle odrazu, na jiném místě analyzátoru oříznutí.
// 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();
}
Dynamická Závislost
Atribut [DynamicDependency] lze použít k označení, že člen má dynamickou závislost na jiných členech. Výsledkem je, že odkazovaní členové jsou zachováni vždy, když je zachován člen s atributem, ale samy o sobě to neumlčuje varování. Na rozdíl od ostatních atributů, které informují analýzu oříznutí o chování reflexe kódu, [DynamicDependency] pouze uchovává ostatní členy. Toto lze použít spolu s [UnconditionalSuppressMessage] k opravě některých upozornění na analýzu.
Upozornění
Atribut používejte [DynamicDependency] pouze jako poslední možnost, pokud ostatní přístupy nejsou přijatelné. Je vhodnější vyjádřit chování reflexe pomocí [RequiresUnreferencedCode] nebo [DynamicallyAccessedMembers].
[DynamicDependency("Helper", "MyType", "MyAssembly")]
static void RunHelper()
{
var helper = Assembly.Load("MyAssembly").GetType("MyType").GetMethod("Helper");
helper.Invoke(null, null);
}
Bez DynamicDependency může oříznutí odebrat Helper z MyAssembly nebo úplně odebrat MyAssembly, pokud se na něj jinde neodkazuje, což vytvoří upozornění, které naznačuje možné selhání za běhu. Atribut zajišťuje, že Helper se zachová.
Atribut určuje členy, které mají být zachovány prostřednictvím string nebo prostřednictvím DynamicallyAccessedMemberTypes. Typ a sestavení jsou buď implicitní v kontextu atributu, nebo explicitně zadané v atributu (podle Type, nebo podle strings pro typ a název sestavení).
Typy a řetězce členů používají variantu identifikátoru řetězce komentáře dokumentace jazyka C# formát, bez předpony členů. Řetězec člena by neměl obsahovat název deklarujícího typu a může vynechat parametry, aby se zachovaly všechny členy zadaného názvu. Některé příklady formátu jsou uvedeny v následujícím kódu:
[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<>))]
Atribut [DynamicDependency] je navržen tak, aby byl použit v případech, kdy metoda obsahuje vzory reflexe, které nelze analyzovat ani s pomocí DynamicallyAccessedMembersAttribute.