Novinky v modulu runtime .NET 8
Tento článek popisuje nové funkce modulu runtime .NET pro .NET 8.
Vylepšení výkonu
.NET 8 zahrnuje vylepšení generování kódu a kompilace JIT (just-in time):
- Vylepšení výkonu Arm64
- Vylepšení SIMD
- Podpora rozšíření AVX-512 ISA (viz Vector512 a AVX-512)
- Vylepšení pro cloudové prostředí
- Vylepšení propustnosti JIT
- Optimalizace smyček a obecné optimalizace
- Optimalizovaný přístup pro pole označená ThreadStaticAttribute
- Po sobě jdoucí přidělení registru. Arm64 má dvě instrukce pro vyhledávání vektorových tabulek, které vyžadují, aby všechny entity v jejich uskupení operandů byly přítomny v po sobě jdoucích registrech.
- JIT/NativeAOT nyní může rozvinout a automaticky vektorizovat některé operace paměti s pomocí SIMD, jako je porovnání, kopírování a nulování, pokud dokáže při kompilaci určit jejich velikosti.
Kromě toho se vylepšila dynamická optimalizace s asistencí profilu (PGO) a je teď ve výchozím nastavení povolená. K jeho povolení už nemusíte používat možnost konfigurace modulu runtime . Dynamické PGO pracuje v úzké spolupráci s vrstvenou kompilací pro další optimalizaci kódu na základě dodatečné instrumentace, která je zavedená během tier 0.
V průměru dynamické PGO zvyšuje výkon o přibližně 15%. V srovnávací sadě testů ~4600 došlo ke zlepšení výkonu u 23% o 20% a více.
Optimalizace struktury Codegen
.NET 8 obsahuje nový optimalizační průchod fyzické propagace pro generování kódu, který generalizuje schopnost JIT promítnout proměnné struktury. Tato optimalizace (označovaná také jako skalární nahrazení agregací) nahrazuje pole strukturálních proměnných primitivními proměnnými, které JIT analyticky zpracovává a optimalizuje přesněji.
JIT už tuto optimalizaci podporuje, ale s několika velkými omezeními, mezi které patří:
- Byla podporována pouze pro struktury se čtyřmi nebo méně poli.
- Podporovalo se pouze v případě, že každé pole bylo primitivním typem nebo jednoduchou strukturou, která obtéká primitivní typ.
Fyzické povýšení odebere tato omezení, která řeší řadu dlouhodobých problémů s JIT.
Sběr odpadků
.NET 8 přidává funkci pro úpravu limitu paměti za běhu. To je užitečné ve scénářích cloudových služeb, ve kterých přichází a přechází poptávka. Aby byly nákladově efektivní, měly by se služby přizpůsobovat ve spotřebě prostředků podle kolísající poptávky. Když služba zjistí snížení poptávky, může snížit spotřebu prostředků snížením kapacity snížením limitu paměti. Dříve se to nezdařilo, protože garbage collector (GC) nevědělo o změně a mohlo by tak přidělit více paměti, než byl nový limit. Pomocí této změny můžete volat rozhraní API RefreshMemoryLimit() a aktualizovat GC novým limitem paměti.
Existuje několik omezení, o nichž je potřeba vědět:
- Na 32bitových platformách (například Windows x86 a Linux ARM) nemůže .NET stanovit nový pevný limit pro haldu, pokud ještě neexistuje.
- Rozhraní API může vrátit nenulový stavový kód označující selhání aktualizace. K tomu může dojít, pokud je vertikální snížení kapacity příliš agresivní a neopustí prostor pro manévrování GC. V takovém případě zvažte volání
GC.Collect(2, GCCollectionMode.Aggressive)
ke zmenšení aktuálního využití paměti a akci opakujte. - Pokud navýšíte limit paměti nad rámec velikosti, kterou GC považuje za schopnou být zpracována během spouštění, bude volání
RefreshMemoryLimit
úspěšné, ale nebude moci využívat více paměti, než považuje za limit.
Následující fragment kódu ukazuje, jak volat rozhraní API.
GC.RefreshMemoryLimit();
Můžete také aktualizovat některá nastavení konfigurace GC související s limitem paměti. Následující fragment kódu nastaví pevný limit haldy na 100 mebibajtů (MiB):
AppContext.SetData("GCHeapHardLimit", (ulong)100 * 1_024 * 1_024);
GC.RefreshMemoryLimit();
Rozhraní API může vyvolat InvalidOperationException, pokud je pevný limit neplatný, například v případě záporných procent pevných limitů haldy a pokud je pevný limit příliš nízký. K tomu může dojít v případě, že pevná mez haldy, kterou aktualizace nastaví, ať už kvůli novým nastavením AppData, nebo změnám limitu paměti kontejneru, je nižší než hodnota, která je již alokována.
Globalizace pro mobilní aplikace
Mobilní aplikace v systémech iOS, tvOS a MacCatalyst se můžou přihlásit k novému hybridnímu režimu globalizace, který používá odlehčený balíček ICU. V hybridním režimu se data globalizace částečně načítá ze sady ICU a částečně z volání do nativních rozhraní API. Hybridní režim obsluhuje všechna národní prostředí podporovaná mobilními.
Hybridní režim je nejvhodnější pro aplikace, které nemohou fungovat v nereagujícím režimu globalizace a používají kulturní nastavení, která byla na mobilních platformách oříznuta z dat ICU. Můžete ho také použít, když chcete načíst menší datový soubor ICU. (Soubor icudt_hybrid.dat je 34,5 % menší než výchozí datový soubor ICU icudt.dat.)
Chcete-li použít režim hybridní globalizace, nastavte HybridGlobalization
MSBuild vlastnost na hodnotu true:
<PropertyGroup>
<HybridGlobalization>true</HybridGlobalization>
</PropertyGroup>
Existuje několik omezení, o nichž je potřeba vědět:
- Vzhledem k omezením nativního rozhraní API nejsou v hybridním režimu podporována všechna rozhraní API globalizace.
- Některá podporovaná rozhraní API mají jiné chování.
Pokud chcete zkontrolovat, jestli je aplikace ovlivněná, přečtěte si rozdíly v chování.
Zprostředkování interakce COM generované zdrojem
.NET 8 zahrnuje nový generátor zdrojového kódu, který podporuje interoperabilitu s rozhraními COM. Pomocí GeneratedComInterfaceAttribute můžete označit rozhraní jako rozhraní COM pro zdrojový generátor. Generátor zdrojů pak vygeneruje kód, který povolí volání z kódu jazyka C# do nespravovaného kódu. Také vygeneruje kód, který povolí volání z nespravovaného kódu do C#. Tento generátor zdroje se integruje s LibraryImportAttributea můžete použít typy s GeneratedComInterfaceAttribute jako parametry a návratové hodnoty v metodách s atributem LibraryImport
.
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
[GeneratedComInterface]
[Guid("5401c312-ab23-4dd3-aa40-3cb4b3a4683e")]
partial interface IComInterface
{
void DoWork();
}
internal partial class MyNativeLib
{
[LibraryImport(nameof(MyNativeLib))]
public static partial void GetComInterface(out IComInterface comInterface);
}
Generátor zdrojového kódu také podporuje nový atribut GeneratedComClassAttribute, který umožňuje předávat typy, které implementují rozhraní s atributem GeneratedComInterfaceAttribute do nespravovaného kódu. Zdrojový generátor vygeneruje kód potřebný k vystavení objektu COM, který implementuje rozhraní a předává volání spravované implementaci.
Metody v rozhraních s atributem GeneratedComInterfaceAttribute podporují všechny stejné typy jako LibraryImportAttribute
a LibraryImportAttribute
nyní podporuje typy GeneratedComInterface
-attributed a typy GeneratedComClass
-attributed.
Pokud váš kód jazyka C# používá pouze GeneratedComInterface
-attributed rozhraní k zabalení buď objektu COM z nespravovaného kódu, nebo spravovaného objektu z jazyka C# k jeho zpřístupnění pro nespravovaný kód, můžete použít možnosti ve vlastnosti Options k přizpůsobení toho, který kód bude vygenerován. Tyto možnosti znamenají, že nemusíte psát marshallery pro scénáře, o kterých víte, že se nebudou používat.
Generátor zdrojů používá nový typ StrategyBasedComWrappers k vytvoření a správě obalů objektů COM a obalů spravovaných objektů. Tento nový typ zpracovává poskytování očekávaného uživatelského prostředí .NET pro interoperabilitu modelu COM a poskytuje body přizpůsobení pro pokročilé uživatele. Pokud má vaše aplikace vlastní mechanismus pro definování typů z modelu COM nebo pokud potřebujete podporovat scénáře, které zdrojový objekt COM v současné době nepodporuje, zvažte použití nového typu StrategyBasedComWrappers k přidání chybějících funkcí pro váš scénář a získání stejného uživatelského prostředí .NET pro vaše typy modelu COM.
Pokud používáte Visual Studio, nové analyzátory a opravy kódu usnadňují převod vašeho stávajícího COM interop kódu na interop generovaný ze zdroje. Vedle každého rozhraní, které má ComImportAttribute, žárovka nabízí možnost převést na zdrojově generovaný interop. Oprava změní rozhraní tak, aby používalo atribut GeneratedComInterfaceAttribute. A vedle každé třídy, která implementuje rozhraní s GeneratedComInterfaceAttribute
, žárovka nabízí možnost přidat atribut GeneratedComClassAttribute k typu. Po převodu typů můžete přesunout metody DllImport
k používání LibraryImportAttribute
.
Omezení
Zdrojový generátor modelu COM nepodporuje apartmánový model, pro aktivaci CoClass pomocí klíčového slova new
a následující API:
- IDispatchzaložená rozhraní.
- Rozhraní založená na IInspectable.
- Vlastnosti a události modelu COM.
Generátor zdrojového kódu pro vazby konfigurace
.NET 8 zavádí generátor zdrojů, který poskytuje konfiguraci přívětivou pro AOT a oříznutí v ASP.NET Core. Generátor je alternativou k existující implementaci založené na reflexi.
Zdrojový generátor provádí sondování pro volání Configure(TOptions), Binda Get, aby z nich načetl informace o typu. Pokud je generátor povolen v projektu, kompilátor implicitně zvolí generované metody nad předem existujícími implementacemi architektury založené na reflexi.
K použití generátoru nejsou potřeba žádné změny zdrojového kódu. Ve výchozím nastavení je povolená ve webových aplikacích kompilovaných AOT a když je PublishTrimmed
nastavená na true
(aplikace .NET 8 a novější). U jiných typů projektů je zdrojový generátor ve výchozím nastavení vypnutý, ale můžete se přihlásit nastavením vlastnosti EnableConfigurationBindingGenerator
na true
v souboru projektu:
<PropertyGroup>
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
</PropertyGroup>
Následující kód ukazuje příklad vyvolání svazovače.
public class ConfigBindingSG
{
static void RunIt(params string[] args)
{
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
IConfigurationSection section = builder.Configuration.GetSection("MyOptions");
// !! Configure call - to be replaced with source-gen'd implementation
builder.Services.Configure<MyOptions>(section);
// !! Get call - to be replaced with source-gen'd implementation
MyOptions? options0 = section.Get<MyOptions>();
// !! Bind call - to be replaced with source-gen'd implementation
MyOptions options1 = new();
section.Bind(options1);
WebApplication app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
}
public class MyOptions
{
public int A { get; set; }
public string S { get; set; }
public byte[] Data { get; set; }
public Dictionary<string, string> Values { get; set; }
public List<MyClass> Values2 { get; set; }
}
public class MyClass
{
public int SomethingElse { get; set; }
}
}
Základní knihovny .NET
Tato část obsahuje následující dílčí témata:
- reflexe
- serializace
- abstrakce času
- vylepšení UTF8
- Metody pro práci s náhodností
- typy zaměřené na výkon
- System.Numerics a System.Runtime.Intrinsics
- ověření dat
- metrik
- kryptografie
- sítě
- metody ZipFile založené na streamu
Reflexe
ukazatele funkcí byly zavedeny v .NET 5, ale odpovídající podpora reflexe nebyla v té době přidána. Při použití typeof
nebo odrazu ukazatele funkce, například typeof(delegate*<void>())
nebo FieldInfo.FieldType
, byl vrácen IntPtr. Počínaje rozhraním .NET 8 se místo toho vrátí objekt System.Type. Tento typ poskytuje přístup k metadatům ukazatelů funkce, včetně konvencí volání, návratového typu a parametrů.
Poznámka
Instance ukazatele funkce, což je fyzická adresa funkce, je nadále reprezentována jako IntPtr. Změnil se pouze typ odrazu.
Nové funkce jsou v současné době implementovány pouze v modulu runtime CoreCLR a MetadataLoadContext.
Do System.Typebyly přidána nová rozhraní API, jako jsou IsFunctionPointer, System.Reflection.PropertyInfo, System.Reflection.FieldInfoa System.Reflection.ParameterInfo. Následující kód ukazuje, jak použít některá nová rozhraní API k reflexi.
using System;
using System.Reflection;
// Sample class that contains a function pointer field.
public unsafe class UClass
{
public delegate* unmanaged[Cdecl, SuppressGCTransition]<in int, void> _fp;
}
internal class FunctionPointerReflection
{
public static void RunIt()
{
FieldInfo? fieldInfo = typeof(UClass).GetField(nameof(UClass._fp));
// Obtain the function pointer type from a field.
Type? fpType = fieldInfo?.FieldType;
// New methods to determine if a type is a function pointer.
Console.WriteLine(
$"IsFunctionPointer: {fpType?.IsFunctionPointer}");
Console.WriteLine(
$"IsUnmanagedFunctionPointer: {fpType?.IsUnmanagedFunctionPointer}");
// New methods to obtain the return and parameter types.
Console.WriteLine($"Return type: {fpType?.GetFunctionPointerReturnType()}");
if (fpType is not null)
{
foreach (Type parameterType in fpType.GetFunctionPointerParameterTypes())
{
Console.WriteLine($"Parameter type: {parameterType}");
}
}
// Access to custom modifiers and calling conventions requires a "modified type".
Type? modifiedType = fieldInfo?.GetModifiedFieldType();
// A modified type forwards most members to its underlying type.
Type? normalType = modifiedType?.UnderlyingSystemType;
if (modifiedType is not null)
{
// New method to obtain the calling conventions.
foreach (Type callConv in modifiedType.GetFunctionPointerCallingConventions())
{
Console.WriteLine($"Calling convention: {callConv}");
}
}
// New method to obtain the custom modifiers.
Type[]? modifiers =
modifiedType?.GetFunctionPointerParameterTypes()[0].GetRequiredCustomModifiers();
if (modifiers is not null)
{
foreach (Type modreq in modifiers)
{
Console.WriteLine($"Required modifier for first parameter: {modreq}");
}
}
}
}
Předchozí příklad vytvoří následující výstup:
IsFunctionPointer: True
IsUnmanagedFunctionPointer: True
Return type: System.Void
Parameter type: System.Int32&
Calling convention: System.Runtime.CompilerServices.CallConvSuppressGCTransition
Calling convention: System.Runtime.CompilerServices.CallConvCdecl
Required modifier for first parameter: System.Runtime.InteropServices.InAttribute
Serializace
Mnoho vylepšení bylo provedeno ve funkcionalitě serializace a deserializace System.Text.Json v rámci .NET 8. Můžete například přizpůsobit zpracování vlastností JSON, které nejsou vPOCO.
Následující části popisují další vylepšení serializace:
- integrovaná podpora dalších typů
- generátor zdrojů
- hierarchie rozhraní
- zásady pojmenování
- Vlastnosti pouze pro čtení
- Zakázat výchozí nastavení založené na reflexi
- nové metody rozhraní JSONNode API
- neveřejní členové
- rozhraní API pro deserializaci datového proudu
- metoda rozšíření WithAddedModifier
- Nová přetížení JsonContent.Create
- Zmrazení instance JsonSerializerOptions
Další informace o serializaci JSON obecně naleznete v tématu serializace JSON a deserializace v rozhraní .NET.
Integrovaná podpora dalších typů
Serializátor má integrovanou podporu pro následující další typy.
Half, Int128a UInt128 typy čísel.
Console.WriteLine(JsonSerializer.Serialize( [ Half.MaxValue, Int128.MaxValue, UInt128.MaxValue ] )); // [65500,170141183460469231731687303715884105727,340282366920938463463374607431768211455]
Memory<T> a ReadOnlyMemory<T> hodnoty.
byte
hodnoty jsou serializovány do řetězců Base64 a další typy do polí JSON.JsonSerializer.Serialize<ReadOnlyMemory<byte>>(new byte[] { 1, 2, 3 }); // "AQID" JsonSerializer.Serialize<Memory<int>>(new int[] { 1, 2, 3 }); // [1,2,3]
Generátor zdrojů
.NET 8 zahrnuje vylepšení generátoru zdroje System.Text.Json , které jsou zaměřeny na to, aby nativní prostředí AOT na stejné úrovni jako serializátor založený na reflexi. Například:
Zdrojový generátor nyní podporuje serializaci typů s
required
ainit
vlastnostmi. Oba byly již podporovány v serializaci založené na reflexi.Vylepšené formátování zdrojového kódu
parita funkcí JsonSourceGenerationOptionsAttribute s JsonSerializerOptions. Další informace najdete v tématu Určení možností (generování zdroje).
Další diagnostika (například SYSLIB1034 a SYSLIB1039).
Nezahrnujte typy ignorovaných nebo nepřístupných vlastností.
Podpora vnoření deklarací
JsonSerializerContext
v rámci libovolného typu.Podpora vygenerovaných kompilátorem nebo nepopsatelných typů ve scénářích generování slabě zadaných zdrojů. Vzhledem k tomu, že generátor zdrojového kódu nemůže explicitně určit typy generované kompilátorem, System.Text.Json nyní provádí rozlišení nejbližšího předka za běhu. Toto rozlišení určuje nejvhodnější supertyp, se kterým se má hodnota serializovat.
Nový typ převaděče
JsonStringEnumConverter<TEnum>
. Nativní AOT nepodporuje existující třídu JsonStringEnumConverter. Typy výčtu můžete komentovat následujícím způsobem:[JsonConverter(typeof(JsonStringEnumConverter<MyEnum>))] public enum MyEnum { Value1, Value2, Value3 } [JsonSerializable(typeof(MyEnum))] public partial class MyContext : JsonSerializerContext { }
Další informace naleznete v tématu Serializovat výčtová pole jako řetězce.
Nová vlastnost
JsonConverter.Type
umožňuje vyhledat typ ne generické instanceJsonConverter
:Dictionary<Type, JsonConverter> CreateDictionary(IEnumerable<JsonConverter> converters) => converters.Where(converter => converter.Type != null) .ToDictionary(converter => converter.Type!);
Vlastnost je nuleovatelná, protože vrací
null
pro instanceJsonConverterFactory
atypeof(T)
pro instanceJsonConverter<T>
.
Řetězení generátorů zdrojů
Třída JsonSerializerOptions obsahuje novou vlastnost TypeInfoResolverChain, která doplňuje stávající vlastnost TypeInfoResolver. Tyto vlastnosti se používají k přizpůsobení smluv pro řetězení zdrojových generátorů. Přidání nové vlastnosti znamená, že nemusíte zadávat všechny zřetězené komponenty v jednom připojení volání – mohou být přidány dodatečně. TypeInfoResolverChain také umožňuje introspekci řetězu nebo odebrání součástí. Další informace naleznete v tématu Kombinování generátorů zdrojů.
Kromě toho je JsonSerializerOptions.AddContext<TContext>() zastaralé. Byla nahrazena vlastnostmi TypeInfoResolver a TypeInfoResolverChain. Další informace najdete v tématu SYSLIB0049.
Hierarchie rozhraní
.NET 8 přidává podporu serializace vlastností z hierarchií rozhraní.
Následující kód ukazuje příklad, kde vlastnosti z okamžitě implementovaného rozhraní a jeho základního rozhraní jsou serializovány.
public static void InterfaceHierarchies()
{
IDerived value = new DerivedImplement { Base = 0, Derived = 1 };
string json = JsonSerializer.Serialize(value);
Console.WriteLine(json); // {"Derived":1,"Base":0}
}
public interface IBase
{
public int Base { get; set; }
}
public interface IDerived : IBase
{
public int Derived { get; set; }
}
public class DerivedImplement : IDerived
{
public int Base { get; set; }
public int Derived { get; set; }
}
Zásady pojmenování
JsonNamingPolicy
zahrnuje nové zásady pojmenování pro snake_case
(s podtržítkem) a kebab-case
(s pomlčkou) pro převody názvů vlastností. Použijte tyto zásady podobně jako existující zásady JsonNamingPolicy.CamelCase:
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
};
JsonSerializer.Serialize(new { PropertyName = "value" }, options);
// { "property_name" : "value" }
Další informace najdete v tématu Použití předdefinovaných zásad pojmenování.
Vlastnosti jen pro čtení
Nyní můžete deserializovat do polí nebo vlastností pouze pro čtení (tj. těch, které nemají přistupovač set
).
Pokud se chcete přihlásit k této podpoře globálně, nastavte novou možnost PreferredObjectCreationHandlingna JsonObjectCreationHandling.Populate. Pokud je problémem kompatibilita, můžete funkci povolit podrobněji tím, že umístíte atribut [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
na konkrétní typy, jejichž vlastnosti se mají naplnit, nebo u jednotlivých vlastností.
Představte si například následující kód, který deserializuje do typu CustomerInfo
, který má dvě vlastnosti jen pro čtení.
public static void ReadOnlyProperties()
{
CustomerInfo customer = JsonSerializer.Deserialize<CustomerInfo>("""
{ "Names":["John Doe"], "Company":{"Name":"Contoso"} }
""")!;
Console.WriteLine(JsonSerializer.Serialize(customer));
}
class CompanyInfo
{
public required string Name { get; set; }
public string? PhoneNumber { get; set; }
}
[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
class CustomerInfo
{
// Both of these properties are read-only.
public List<string> Names { get; } = new();
public CompanyInfo Company { get; } = new()
{
Name = "N/A",
PhoneNumber = "N/A"
};
}
Před rozhraním .NET 8 se vstupní hodnoty ignorovaly a vlastnosti Names
a Company
zachovaly výchozí hodnoty.
{"Names":[],"Company":{"Name":"N/A","PhoneNumber":"N/A"}}
Nyní se vstupní hodnoty používají k naplnění vlastností jen pro čtení během deserializace.
{"Names":["John Doe"],"Company":{"Name":"Contoso","PhoneNumber":"N/A"}}
Další informace o naplnění deserializační chování naleznete v tématu Naplnění inicializovaných vlastností.
Zakázání výchozího nastavení založeného na reflexi
Nyní můžete zakázat použití serializátoru založeného na reflexi ve výchozím nastavení. Zakázání této funkce je užitečné, pokud se chcete vyhnout náhodnému zakořenění reflexních komponent, které se vůbec nepoužívají, zejména v odlehčených a nativních AOT aplikacích. Chcete-li zakázat výchozí serializaci založenou na reflexi tím, že vyžaduje předání JsonSerializerOptions argumentu metodám pro serializaci a deserializaci JsonSerializer, nastavte vlastnost JsonSerializerIsReflectionEnabledByDefault
MSBuild na hodnotu false
ve vašem projektovém souboru.
Pomocí nového rozhraní API IsReflectionEnabledByDefault zkontrolujte hodnotu přepínače funkcí. Pokud vytváříte knihovnu, která je založená na System.Text.Json, můžete se spolehnout na vlastnost a nakonfigurovat výchozí hodnoty bez náhodného rootingu součástí reflexe.
Další informace najdete v tématu Zakázání výchozích hodnot reflexe.
Nové metody rozhraní JSONNode API
Typy JsonNode a System.Text.Json.Nodes.JsonArray zahrnují následující nové metody.
public partial class JsonNode
{
// Creates a deep clone of the current node and all its descendants.
public JsonNode DeepClone();
// Returns true if the two nodes are equivalent JSON representations.
public static bool DeepEquals(JsonNode? node1, JsonNode? node2);
// Determines the JsonValueKind of the current node.
public JsonValueKind GetValueKind(JsonSerializerOptions options = null);
// If node is the value of a property in the parent
// object, returns its name.
// Throws InvalidOperationException otherwise.
public string GetPropertyName();
// If node is the element of a parent JsonArray,
// returns its index.
// Throws InvalidOperationException otherwise.
public int GetElementIndex();
// Replaces this instance with a new value,
// updating the parent object/array accordingly.
public void ReplaceWith<T>(T value);
// Asynchronously parses a stream as UTF-8 encoded data
// representing a single JSON value into a JsonNode.
public static Task<JsonNode?> ParseAsync(
Stream utf8Json,
JsonNodeOptions? nodeOptions = null,
JsonDocumentOptions documentOptions = default,
CancellationToken cancellationToken = default);
}
public partial class JsonArray
{
// Returns an IEnumerable<T> view of the current array.
public IEnumerable<T> GetValues<T>();
}
Neveřejní členové
Nepřístupné členy můžete zahrnout do serializačního kontraktu pro daný typ pomocí anotací atributů JsonIncludeAttribute a JsonConstructorAttribute.
public static void NonPublicMembers()
{
string json = JsonSerializer.Serialize(new MyPoco(42));
Console.WriteLine(json);
// {"X":42}
JsonSerializer.Deserialize<MyPoco>(json);
}
public class MyPoco
{
[JsonConstructor]
internal MyPoco(int x) => X = x;
[JsonInclude]
internal int X { get; }
}
Další informace naleznete v tématu Použití neměnných typů a neveřejných členů a přístupových objektů.
Rozhraní API pro deserializaci streamů
.NET 8 obsahuje nové metody rozšíření streamované deserializace IAsyncEnumerable<T>, například GetFromJsonAsAsyncEnumerable. Podobné metody existovaly, které vrací Task<TResult>, například HttpClientJsonExtensions.GetFromJsonAsync. Nové metody rozšíření vyvolávají streamovací API a vrací IAsyncEnumerable<T>.
Následující kód ukazuje, jak můžete použít nové metody rozšíření.
public async static void StreamingDeserialization()
{
const string RequestUri = "https://api.contoso.com/books";
using var client = new HttpClient();
IAsyncEnumerable<Book?> books = client.GetFromJsonAsAsyncEnumerable<Book>(RequestUri);
await foreach (Book? book in books)
{
Console.WriteLine($"Read book '{book?.title}'");
}
}
public record Book(int id, string title, string author, int publishedYear);
Metoda rozšíření WithAddedModifier
Nová metoda rozšíření WithAddedModifier(IJsonTypeInfoResolver, Action<JsonTypeInfo>) umožňuje snadno zavést úpravy serializačních kontraktů libovolných IJsonTypeInfoResolver
instancí.
var options = new JsonSerializerOptions
{
TypeInfoResolver = MyContext.Default
.WithAddedModifier(static typeInfo =>
{
foreach (JsonPropertyInfo prop in typeInfo.Properties)
{
prop.Name = prop.Name.ToUpperInvariant();
}
})
};
Nové přetížení JsonContent.Create
Teď můžete vytvářet JsonContent instance pomocí kontraktů vygenerovaných pomocí trim-safe nebo zdrojových kontraktů. Nové metody jsou:
- JsonContent.Create(Object, JsonTypeInfo, MediaTypeHeaderValue)
- JsonContent.Create<T>(T, JsonTypeInfo<T>, MediaTypeHeaderValue)
var book = new Book(id: 42, "Title", "Author", publishedYear: 2023);
HttpContent content = JsonContent.Create(book, MyContext.Default.Book);
public record Book(int id, string title, string author, int publishedYear);
[JsonSerializable(typeof(Book))]
public partial class MyContext : JsonSerializerContext
{
}
Zablokování instance JsonSerializerOptions
Následující nové metody umožňují řídit, kdy je instance JsonSerializerOptions zablokovaná:
JsonSerializerOptions.MakeReadOnly()
Toto přetížení je navrženo tak, aby bylo bezpečné proti optimalizaci, a proto vyvolá výjimku v případech, kdy instance možností nebyla nakonfigurována resolverem.
JsonSerializerOptions.MakeReadOnly(Boolean)
Pokud předáte
true
tomuto přetížení, naplní instanci možností výchozí řešitel reflexe, pokud chybí. Tato metoda je označenaRequiresUnreferenceCode
/RequiresDynamicCode
a proto není vhodná pro nativní aplikace AOT.
Nová vlastnost IsReadOnly umožňuje zkontrolovat, jestli je instance možností zablokovaná.
Abstrakce času
Nové TimeProvider třídy a rozhraní ITimer přidává časové abstrakce funkce, což umožňuje napodobení času v testovacích scénářích. Kromě toho můžete operace Task, které spoléhají na průběh času, napodobit pomocí abstrakce času, Task.Delay a Task.WaitAsync. Abstrakce času podporuje následující základní časové operace:
- Načtení místního času a času UTC
- Získání časového razítka pro měření výkonu
- Vytvoření časovače
Následující fragment kódu ukazuje některé příklady použití.
// Get system time.
DateTimeOffset utcNow = TimeProvider.System.GetUtcNow();
DateTimeOffset localNow = TimeProvider.System.GetLocalNow();
TimerCallback callback = s => ((State)s!).Signal();
// Create a timer using the time provider.
ITimer timer = _timeProvider.CreateTimer(
callback, null, TimeSpan.Zero, Timeout.InfiniteTimeSpan);
// Measure a period using the system time provider.
long providerTimestamp1 = TimeProvider.System.GetTimestamp();
long providerTimestamp2 = TimeProvider.System.GetTimestamp();
TimeSpan period = _timeProvider.GetElapsedTime(providerTimestamp1, providerTimestamp2);
// Create a time provider that works with a
// time zone that's different than the local time zone.
private class ZonedTimeProvider(TimeZoneInfo zoneInfo) : TimeProvider()
{
private readonly TimeZoneInfo _zoneInfo = zoneInfo ?? TimeZoneInfo.Local;
public override TimeZoneInfo LocalTimeZone => _zoneInfo;
public static TimeProvider FromLocalTimeZone(TimeZoneInfo zoneInfo) =>
new ZonedTimeProvider(zoneInfo);
}
Vylepšení UTF8
Pokud chcete povolit psaní řetězcového vyjádření typu do cílového úseku, implementujte na vašem typu nové rozhraní IUtf8SpanFormattable. Toto nové rozhraní úzce souvisí s ISpanFormattable, ale cílí na UTF8 a Span<byte>
místo UTF16 a Span<char>
.
IUtf8SpanFormattable byla implementována ve všech primitivních typech (plus ostatní) se stejnou sdílenou logikou, ať už cílí na string
, Span<char>
nebo Span<byte>
. Má kompletní podporu pro všechny formáty (včetně nového binárního specifikátoru "B") a všechny jazykové verze. To znamená, že teď můžete formátovat přímo na UTF8 z Byte
, Complex
, Char
, DateOnly
, DateTime
, DateTimeOffset
, Decimal
, Double
, Guid
, Half
, IPAddress
, IPNetwork
, Int16
, Int32
, Int64
, Int128
, IntPtr
, NFloat
, SByte
, Single
, Rune
, TimeOnly
, TimeSpan
, UInt16
, UInt32
, UInt64
, UInt128
, UIntPtr
a Version
.
Nové Utf8.TryWrite metody poskytují protějšk založený na UTF8 stávajícím metodám MemoryExtensions.TryWrite, které jsou založené na UTF16. Syntaxi interpolovaného řetězce můžete použít k naformátování komplexního výrazu přímo do rozsahu bajtů UTF8, například:
static bool FormatHexVersion(
short major,
short minor,
short build,
short revision,
Span<byte> utf8Bytes,
out int bytesWritten) =>
Utf8.TryWrite(
utf8Bytes,
CultureInfo.InvariantCulture,
$"{major:X4}.{minor:X4}.{build:X4}.{revision:X4}",
out bytesWritten);
Implementace rozpozná IUtf8SpanFormattable na hodnotách formátu a pomocí jejich implementací zapíše reprezentace UTF8 přímo do cílového rozsahu.
Implementace také využívá novou metodu Encoding.TryGetBytes(ReadOnlySpan<Char>, Span<Byte>, Int32), která společně s jeho Encoding.TryGetChars(ReadOnlySpan<Byte>, Span<Char>, Int32) protějškem podporuje kódování a dekódování do cílového rozsahu. Pokud rozsah není dostatečně dlouhý na uložení výsledného stavu, metody místo vyvolání výjimky vrátí false
.
Metody pro práci s náhodností
Typy System.Random a System.Security.Cryptography.RandomNumberGenerator představují dvě nové metody pro práci s náhodností.
GetItems<T>()
Nové metody System.Random.GetItems a System.Security.Cryptography.RandomNumberGenerator.GetItems umožňují náhodně zvolit zadaný počet položek ze vstupní sady. Následující příklad ukazuje, jak použít System.Random.GetItems<T>()
(v instanci poskytované vlastností Random.Shared) k náhodnému vložení 31 položek do pole. Tento příklad lze použít ve hře "Simon", kde hráči musí pamatovat posloupnost barevných tlačítek.
private static ReadOnlySpan<Button> s_allButtons = new[]
{
Button.Red,
Button.Green,
Button.Blue,
Button.Yellow,
};
// ...
Button[] thisRound = Random.Shared.GetItems(s_allButtons, 31);
// Rest of game goes here ...
Smíchat<T>()
Nové metody Random.Shuffle a RandomNumberGenerator.Shuffle<T>(Span<T>) umožňují randomizovat pořadí rozsahu. Tyto metody jsou užitečné pro snížení zaujatosti při trénování ve strojovém učení (tak aby první krok nebyl vždy trénování a poslední krok nebyl vždy testování).
YourType[] trainingData = LoadTrainingData();
Random.Shared.Shuffle(trainingData);
IDataView sourceData = mlContext.Data.LoadFromEnumerable(trainingData);
DataOperationsCatalog.TrainTestData split = mlContext.Data.TrainTestSplit(sourceData);
model = chain.Fit(split.TrainSet);
IDataView predictions = model.Transform(split.TestSet);
// ...
Typy zaměřené na výkon
.NET 8 zavádí několik nových typů zaměřených na zlepšení výkonu aplikací.
Nový obor názvů System.Collections.Frozen zahrnuje typy kolekcí FrozenDictionary<TKey,TValue> a FrozenSet<T>. Tyto typy neumožňují po vytvoření kolekce žádné změny klíčů a hodnot. Tento požadavek umožňuje rychlejší operace čtení (například
TryGetValue()
). Tyto typy jsou zvláště užitečné pro kolekce, které se naplní při prvním použití a poté zůstávají po celou dobu dlouhotrvající služby, například:private static readonly FrozenDictionary<string, bool> s_configurationData = LoadConfigurationData().ToFrozenDictionary(); // ... if (s_configurationData.TryGetValue(key, out bool setting) && setting) { Process(); }
Metody jako MemoryExtensions.IndexOfAny hledají první výskyt libovolnou hodnotu v předané kolekci. Nový typ System.Buffers.SearchValues<T> je navržen tak, aby byl předán těmto metodám. Rozhraní .NET 8 odpovídajícím způsobem přidává nová přetížení metod, jako je MemoryExtensions.IndexOfAny, které přijímají instanci nového typu. Při vytváření instance SearchValues<T>jsou všechna data potřebná k optimalizaci následných hledání odvozena v té době, což znamená, že se práce provádí předem.
Nový typ System.Text.CompositeFormat je užitečný pro optimalizaci formátových řetězců, které nejsou v době kompilace známé (například pokud je formátovací řetězec načten ze souboru prostředků). Je předem vynaloženo trochu více času na práci, jako je třeba parsování řetězce, ale ušetří to práci při každém použití.
private static readonly CompositeFormat s_rangeMessage = CompositeFormat.Parse(LoadRangeMessageResource()); // ... static string GetMessage(int min, int max) => string.Format(CultureInfo.InvariantCulture, s_rangeMessage, min, max);
Nové typy System.IO.Hashing.XxHash3 a System.IO.Hashing.XxHash128 poskytují implementace rychlých hash algoritmů XXH3 a XXH128.
System.Numerics a System.Runtime.Intrinsics
Tato část popisuje vylepšení jmenných prostorů System.Numerics a System.Runtime.Intrinsics.
-
Vector256<T>, Matrix3x2a Matrix4x4 mají vylepšenou hardwarovou akceleraci v .NET 8. Například Vector256<T> bylo znovu zkompilováno na interní operace
2x Vector128<T>
, pokud je to možné. To umožňuje částečnou akceleraci některých funkcí, když jeVector128.IsHardwareAccelerated == true
, aleVector256.IsHardwareAccelerated == false
, jako na Arm64. - Hardwarové vnitřní objekty jsou nyní opatřeny poznámkami s atributem
ConstExpected
. To zajišťuje, že uživatelé budou vědět, kdy základní hardware očekává konstantní hodnotu, a tudíž kdy nekonstantní hodnota může nepředpokládaně ovlivnit výkon. - Rozhraní API Lerp(TSelf, TSelf, TSelf)
Lerp
bylo přidáno do IFloatingPointIeee754<TSelf>, a proto dofloat
(Single),double
(Double) a Half. Toto rozhraní API umožňuje efektivní a správné provedení lineární interpolace mezi dvěma hodnotami.
Vector512 a AVX-512
.NET Core 3.0 rozšířil podporu SIMD o rozhraní API hardwarových vnitřních objektů specifických pro platformu x86/x64. Přidání podpory .NET 5 pro Arm64 a .NET 7 přidalo hardwarové vnitřní funkce pro různé platformy. .NET 8 dále rozvíjí podporu SIMD tím, že zavádí Vector512<T> a podporu pro instrukce Intel Advanced Vector Extensions 512 (AVX-512).
Konkrétně .NET 8 zahrnuje podporu následujících klíčových funkcí AVX-512:
- 512bitové vektorové operace
- Další 16 registrů SIMD
- Další pokyny pro 128bitové, 256bitové a 512bitové vektory
Pokud máte hardware, který podporuje funkce, pak Vector512.IsHardwareAccelerated nyní hlásí true
.
.NET 8 také přidá do oboru názvů System.Runtime.Intrinsics.X86 několik tříd specifických pro platformu:
- Avx512F (základní)
- Avx512BW (Bajt a Slovo)
- Avx512CD (detekce konfliktů)
- Avx512DQ (dvojslovo a čtyřslovo)
- Avx512Vbmi (pokyny pro manipulaci s bajty vektoru)
Tyto třídy mají stejný obecný tvar jako ostatní architektury instrukční sady (ISA), ve kterých zpřístupňují vlastnost IsSupported a vnořenou třídu Avx512F.X64 pro pokyny dostupné pouze pro 64bitové procesy. Kromě toho má každá třída vnořenou Avx512F.VL třídu, která zveřejňuje rozšíření Avx512VL
(délka vektoru) pro odpovídající instrukční sadu.
I když v kódu explicitně nepoužíváte pokyny specifické pro konkrétní Vector512
nebo Avx512F
, pravděpodobně budete mít stále výhodu z nové podpory AVX-512. JIT může využívat další registry a instrukce implicitně při použití Vector128<T> nebo Vector256<T>. Knihovna základních tříd používá tyto hardwarové instrukce interně ve většině operací spojených s Span<T> a ReadOnlySpan<T> a v mnoha matematických rozhraních API pro primitivní typy.
Ověření dat
Obor názvů System.ComponentModel.DataAnnotations zahrnuje nové atributy ověření dat určené pro scénáře ověřování v cloudových nativních službách. I když jsou již existující DataAnnotations
validátory zaměřené na typické ověřování zadávání dat uživatelského rozhraní, jako jsou pole ve formuláři, jsou nové atributy navrženy k ověření nezadávaných dat, jako jsou možnosti konfigurace . Kromě nových atributů byly do typu RangeAttribute přidány nové vlastnosti.
Nové rozhraní API | Popis |
---|---|
RangeAttribute.MinimumIsExclusive RangeAttribute.MaximumIsExclusive |
Určuje, zda jsou hranice zahrnuty do povoleného rozsahu. |
System.ComponentModel.DataAnnotations.LengthAttribute | Určuje dolní i horní mez pro řetězce nebo kolekce. Například [Length(10, 20)] vyžaduje alespoň 10 prvků a maximálně 20 prvků v kolekci. |
System.ComponentModel.DataAnnotations.Base64StringAttribute | Ověří, zda je řetězec platným vyjádřením Base64. |
System.ComponentModel.DataAnnotations.AllowedValuesAttribute System.ComponentModel.DataAnnotations.DeniedValuesAttribute |
Zadejte seznamy povolení a seznamy zamítnutí. Například [AllowedValues("apple", "banana", "mango")] . |
Metriky
Nová rozhraní API umožňují připojit značky párů klíč-hodnota k Meter a Instrument objektům při jejich vytvoření. Agregátory publikovaných měření metrik můžou používat značky k odlišení agregovaných hodnot.
var options = new MeterOptions("name")
{
Version = "version",
// Attach these tags to the created meter.
Tags = new TagList()
{
{ "MeterKey1", "MeterValue1" },
{ "MeterKey2", "MeterValue2" }
}
};
Meter meter = meterFactory!.Create(options);
Counter<int> counterInstrument = meter.CreateCounter<int>(
"counter", null, null, new TagList() { { "counterKey1", "counterValue1" } }
);
counterInstrument.Add(1);
Mezi nová rozhraní API patří:
- MeterOptions
- Meter(MeterOptions)
- CreateCounter<T>(String, String, String, IEnumerable<KeyValuePair<String,Object>>)
Kryptografie
.NET 8 přidává podporu pro hashovací primitivy SHA-3. (SHA-3 je v současné době podporováno Linuxem s OpenSSL 1.1.1 nebo novějším a Windows 11 verzí 25324 nebo novější.) Rozhraní API, která mají k dispozici SHA-2, nyní nabízejí podporu SHA-3. To zahrnuje SHA3_256
, SHA3_384
a SHA3_512
pro hashování; HMACSHA3_256
, HMACSHA3_384
a HMACSHA3_512
pro HMAC; HashAlgorithmName.SHA3_256
, HashAlgorithmName.SHA3_384
a HashAlgorithmName.SHA3_512
pro hashování, kde je algoritmus konfigurovatelný; a RSAEncryptionPadding.OaepSHA3_256
, RSAEncryptionPadding.OaepSHA3_384
a RSAEncryptionPadding.OaepSHA3_512
pro šifrování RSA OAEP.
Následující příklad ukazuje, jak používat rozhraní API, včetně SHA3_256.IsSupported
vlastnost k určení, jestli platforma podporuje SHA-3.
// Hashing example
if (SHA3_256.IsSupported)
{
byte[] hash = SHA3_256.HashData(dataToHash);
}
else
{
// ...
}
// Signing example
if (SHA3_256.IsSupported)
{
using ECDsa ec = ECDsa.Create(ECCurve.NamedCurves.nistP256);
byte[] signature = ec.SignData(dataToBeSigned, HashAlgorithmName.SHA3_256);
}
else
{
// ...
}
Podpora SHA-3 je v současné době zaměřená na podporu kryptografických primitiv. Konstrukce a protokoly vyšší úrovně se neočekává, že zpočátku plně podporují SHA-3. Mezi tyto protokoly patří certifikáty X.509, SignedXmla COSE.
Síťování
Podpora proxy serveru HTTPS
Až do teď všechny typy proxy, které HttpClient podporuje, umožňovaly útok typu "man-in-the-middle," aby mohl zjistit, ke kterému webu se klient připojuje, i u HTTPS URI. HttpClient teď podporuje proxyHTTPS, který vytvoří šifrovaný kanál mezi klientem a proxy serverem, aby všechny požadavky mohly být zpracovány s úplnou ochranou osobních údajů.
Chcete-li povolit proxy server HTTPS, nastavte all_proxy
proměnnou prostředí nebo použijte třídu WebProxy k řízení proxy programově.
Unix: export all_proxy=https://x.x.x.x:3218
Windows: set all_proxy=https://x.x.x.x:3218
Třídu WebProxy můžete také použít k programovému ovládání proxy.
Metody ZipFile založené na streamu
.NET 8 obsahuje nová přetížení ZipFile.CreateFromDirectory, které vám umožní shromáždit všechny soubory zahrnuté v adresáři a zazipovat je a pak uložit výsledný soubor ZIP do poskytnutého datového proudu. Podobně nové přetížení ZipFile.ExtractToDirectory umožňují poskytnout datový proud obsahující zazipovaný soubor a extrahovat jeho obsah do systému souborů. Toto jsou nová přetížení:
namespace System.IO.Compression;
public static partial class ZipFile
{
public static void CreateFromDirectory(
string sourceDirectoryName, Stream destination);
public static void CreateFromDirectory(
string sourceDirectoryName,
Stream destination,
CompressionLevel compressionLevel,
bool includeBaseDirectory);
public static void CreateFromDirectory(
string sourceDirectoryName,
Stream destination,
CompressionLevel compressionLevel,
bool includeBaseDirectory,
Encoding? entryNameEncoding);
public static void ExtractToDirectory(
Stream source, string destinationDirectoryName) { }
public static void ExtractToDirectory(
Stream source, string destinationDirectoryName, bool overwriteFiles) { }
public static void ExtractToDirectory(
Stream source, string destinationDirectoryName, Encoding? entryNameEncoding) { }
public static void ExtractToDirectory(
Stream source, string destinationDirectoryName, Encoding? entryNameEncoding, bool overwriteFiles) { }
}
Tato nová rozhraní API můžou být užitečná v případě omezení místa na disku, protože se vyhýbají použití disku jako přechodného kroku.
Knihovny rozšíření
Tato část obsahuje následující dílčí témata:
- ověřování možností
- Konstruktor LoggerMessageAttribute
- metriky rozšíření
- hostované služby pro životní cyklus
- keyed DI služby
- System.Numerics.Tensors.TensorPrimitives
Služby DI s klíči
Služby injektáže závislostí s klíči poskytují prostředky pro registraci a načítání služeb DI pomocí klíčů. Pomocí klíčů můžete určit rozsah způsobu registrace a využívání služeb. Toto jsou některá nová rozhraní API:
- Rozhraní IKeyedServiceProvider.
- Atribut ServiceKeyAttribute, který lze použít k vložení klíče použitého k registraci/rozlišení v konstruktoru.
- Atribut FromKeyedServicesAttribute, který lze použít u parametrů konstruktoru služby k určení, kterou klíčovou službu použít.
- Různé nové rozšiřující metody pro IServiceCollection pro podporu klíčových služeb, například ServiceCollectionServiceExtensions.AddKeyedScoped.
- Implementace ServiceProviderIKeyedServiceProvider.
Následující příklad ukazuje, jak používat služby DI s klíči.
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<BigCacheConsumer>();
builder.Services.AddSingleton<SmallCacheConsumer>();
builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
WebApplication app = builder.Build();
app.MapGet("/big", (BigCacheConsumer data) => data.GetData());
app.MapGet("/small", (SmallCacheConsumer data) => data.GetData());
app.MapGet("/big-cache", ([FromKeyedServices("big")] ICache cache) => cache.Get("data"));
app.MapGet("/small-cache", (HttpContext httpContext) => httpContext.RequestServices.GetRequiredKeyedService<ICache>("small").Get("data"));
app.Run();
class BigCacheConsumer([FromKeyedServices("big")] ICache cache)
{
public object? GetData() => cache.Get("data");
}
class SmallCacheConsumer(IServiceProvider serviceProvider)
{
public object? GetData() => serviceProvider.GetRequiredKeyedService<ICache>("small").Get("data");
}
public interface ICache
{
object Get(string key);
}
public class BigCache : ICache
{
public object Get(string key) => $"Resolving {key} from big cache.";
}
public class SmallCache : ICache
{
public object Get(string key) => $"Resolving {key} from small cache.";
}
Další informace najdete v tématu dotnet/runtime#64427.
Hostované služby životního cyklu
Hostované služby teď mají více možností pro spuštění během životního cyklu aplikace.
IHostedService poskytuje StartAsync
a StopAsync
a teď IHostedLifecycleService nabízí tyto další metody:
- StartingAsync(CancellationToken)
- StartedAsync(CancellationToken)
- StoppingAsync(CancellationToken)
- StoppedAsync(CancellationToken)
Tyto metody se spouští před a po existujících bodech.
Následující příklad ukazuje, jak používat nová rozhraní API.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
internal class HostedLifecycleServices
{
public async static void RunIt()
{
IHostBuilder hostBuilder = new HostBuilder();
hostBuilder.ConfigureServices(services =>
{
services.AddHostedService<MyService>();
});
using (IHost host = hostBuilder.Build())
{
await host.StartAsync();
}
}
public class MyService : IHostedLifecycleService
{
public Task StartingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StartAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StartedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StoppedAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
public Task StoppingAsync(CancellationToken cancellationToken) => /* add logic here */ Task.CompletedTask;
}
}
Pro více informací viz dotnet/runtime#86511.
Ověřování možností
Generátor zdrojů
Abychom snížili režii při spuštění a vylepšili sadu funkcí ověřování, zavedli jsme generátor zdrojového kódu, který implementuje logiku ověřování. Následující kód ukazuje ukázkové modely a třídy validátoru.
public class FirstModelNoNamespace
{
[Required]
[MinLength(5)]
public string P1 { get; set; } = string.Empty;
[Microsoft.Extensions.Options.ValidateObjectMembers(
typeof(SecondValidatorNoNamespace))]
public SecondModelNoNamespace? P2 { get; set; }
}
public class SecondModelNoNamespace
{
[Required]
[MinLength(5)]
public string P4 { get; set; } = string.Empty;
}
[OptionsValidator]
public partial class FirstValidatorNoNamespace
: IValidateOptions<FirstModelNoNamespace>
{
}
[OptionsValidator]
public partial class SecondValidatorNoNamespace
: IValidateOptions<SecondModelNoNamespace>
{
}
Pokud vaše aplikace používá injektáž závislostí, můžete ověření vložit, jak je znázorněno v následujícím ukázkovém kódu.
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.Configure<FirstModelNoNamespace>(
builder.Configuration.GetSection("some string"));
builder.Services.AddSingleton<
IValidateOptions<FirstModelNoNamespace>, FirstValidatorNoNamespace>();
builder.Services.AddSingleton<
IValidateOptions<SecondModelNoNamespace>, SecondValidatorNoNamespace>();
Typ ValidateOptionsResultBuilder
.NET 8 zavádí typ ValidateOptionsResultBuilder, který usnadňuje vytvoření ValidateOptionsResult objektu. Důležité je, že tento nástroj umožňuje akumulaci více chyb. Dříve bylo vytvoření objektu ValidateOptionsResult potřebného k uskutečnění IValidateOptions<TOptions>.Validate(String, TOptions) obtížné a někdy vedlo k vrstveným validačním chybám. Pokud došlo k více chybám, proces ověřování se často zastavil při první chybě.
Následující fragment kódu ukazuje příklad použití ValidateOptionsResultBuilder.
ValidateOptionsResultBuilder builder = new();
builder.AddError("Error: invalid operation code");
builder.AddResult(ValidateOptionsResult.Fail("Invalid request parameters"));
builder.AddError("Malformed link", "Url");
// Build ValidateOptionsResult object has accumulating multiple errors.
ValidateOptionsResult result = builder.Build();
// Reset the builder to allow using it in new validation operation.
builder.Clear();
LoggerMessageAttribute – konstruktory
LoggerMessageAttribute nyní nabízí další přetížení konstruktoru. Dříve jste museli zvolit konstruktor bez parametrů nebo konstruktor, který vyžadoval všechny parametry (ID události, úroveň protokolu a zprávu). Nové přetížení funkcí nabízejí větší flexibilitu při zadávání požadovaných parametrů s menším množstvím kódu. Pokud nezadáte ID události, systém ho automaticky vygeneruje.
public LoggerMessageAttribute(LogLevel level, string message);
public LoggerMessageAttribute(LogLevel level);
public LoggerMessageAttribute(string message);
Metriky rozšíření
IMeterFactory – rozhraní
Nové rozhraní IMeterFactory můžete zaregistrovat v kontejnerech injektáže závislostí (DI) a použít ho k vytvoření Meter objektů izolovaným způsobem.
Zaregistrujte IMeterFactory do kontejneru DI pomocí výchozí implementace továrny na měřiče.
// 'services' is the DI IServiceCollection.
services.AddMetrics();
Spotřebitelé pak mohou získat fabriku na měřiče a použít ji k vytvoření nového objektu Meter.
IMeterFactory meterFactory = serviceProvider.GetRequiredService<IMeterFactory>();
MeterOptions options = new MeterOptions("MeterName")
{
Version = "version",
};
Meter meter = meterFactory.Create(options);
Třída<T> MetricCollector
Nová třída MetricCollector<T> umožňuje zaznamenávat měření metrik spolu s časovými razítky. Kromě toho třída nabízí flexibilitu použít poskytovatele času podle vašeho výběru pro přesné generování časových razítek.
const string CounterName = "MyCounter";
DateTimeOffset now = DateTimeOffset.Now;
var timeProvider = new FakeTimeProvider(now);
using var meter = new Meter(Guid.NewGuid().ToString());
Counter<long> counter = meter.CreateCounter<long>(CounterName);
using var collector = new MetricCollector<long>(counter, timeProvider);
Assert.IsNull(collector.LastMeasurement);
counter.Add(3);
// Verify the update was recorded.
Assert.AreEqual(counter, collector.Instrument);
Assert.IsNotNull(collector.LastMeasurement);
Assert.AreSame(collector.GetMeasurementSnapshot().Last(), collector.LastMeasurement);
Assert.AreEqual(3, collector.LastMeasurement.Value);
Assert.AreEqual(now, collector.LastMeasurement.Timestamp);
System.Numerics.Tensors.TensorPrimitives
Aktualizovaný balíček NuGet System.Numerics.Tensors zahrnuje rozhraní API v novém typu System.Numerics.Tensors.TensorPrimitives, která přidávají podporu pro operace tensoru. Primitivní tensor optimalizují úlohy náročné na data, jako jsou úlohy umělé inteligence a strojového učení.
Úlohy umělé inteligence, jako je sémantické vyhledávání a načítání rozšířené generace (RAG), rozšiřují možnosti přirozeného jazyka velkých jazykových modelů, jako je ChatGPT, rozšířením výzev s relevantními daty. Pro tyto úlohy jsou zásadní operace s vektory, jako je kosinová podobnost pro nalezení nejrelevantnějších dat k zodpovězení otázky. Typ TensorPrimitives poskytuje rozhraní API pro vektorové operace.
Další informace naleznete v blogovém příspěvku Oznámení .NET 8 RC 2.
Nativní podpora AOT
Možnost publikovat jako Nativní AOT byla poprvé představena v .NET 7. Publikování aplikace s nativní AOT vytvoří plně samostatnou verzi vaší aplikace, která nepotřebuje modul runtime – všechno je součástí jednoho souboru. .NET 8 přináší následující vylepšení nativního publikování AOT:
Přidává podporu pro architektury x64 a Arm64 v macOS.
Snižuje velikost nativních aplikací AOT v Linuxu až o 50%. Následující tabulka ukazuje velikost aplikace Hello World publikovanou s nativní AOT, která zahrnuje celý modul runtime .NET v .NET 7 a .NET 8:
Operační systém .NET 7 .NET 8 Linux x64 (se -p:StripSymbols=true
)3,76 MB 1,84 MB Windows x64 2,85 MB 1,77 MB Umožňuje určit předvolbu optimalizace: velikost nebo rychlost. Ve výchozím nastavení se kompilátor rozhodne vygenerovat rychlý kód a přitom si uvědomovat velikost aplikace. Můžete však použít tu vlastnost
<OptimizationPreference>
MSBuild k optimalizaci buď pro jeden, nebo pro druhý. Další informace najdete v tématu Optimalizace nasazení AOT.
Cílit na platformy podobné iOS pomocí nativní AOT
.NET 8 spustí práci na povolení nativní podpory AOT pro platformy podobné iOSu. Teď můžete sestavovat a spouštět aplikace .NET iOS a .NET MAUI s nativní AOT na následujících platformách:
ios
iossimulator
maccatalyst
tvos
tvossimulator
Předběžné testování ukazuje, že velikost aplikace na disku se snižuje o přibližně 35% pro aplikace .NET pro iOS, které místo Mono používají nativní AOT. Velikost aplikace na disku pro aplikace .NET MAUI pro iOS se snižuje až o 50%. Kromě toho je čas spuštění rychlejší. Aplikace .NET pro iOS mají přibližně 28% rychlejší spouštění, zatímco aplikace .NET MAUI pro iOS mají v porovnání s Mono přibližně 50% lepší výkon při spuštění. Podpora .NET 8 je experimentální a pouze první krok pro funkci jako celek. Další informace najdete v blogovém příspěvku o vylepšení výkonu .NET 8 v .NET MAUI .
Nativní podpora AOT je dostupná jako funkce výslovného souhlasu určená pro nasazení aplikace; Mono je stále výchozím modulem runtime pro vývoj a nasazení aplikací. Pokud chcete vytvořit a spustit aplikaci .NET MAUI s nativní AOT na zařízení s iOSem, použijte dotnet workload install maui
k instalaci úlohy .NET MAUI a dotnet new maui -n HelloMaui
k vytvoření aplikace. Potom nastavte vlastnost MSBuild PublishAot
na true
v souboru projektu.
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
Když nastavíte požadovanou vlastnost a spustíte dotnet publish
, jak je znázorněno v následujícím příkladu, aplikace se nasadí pomocí nativní AOT.
dotnet publish -f net8.0-ios -c Release -r ios-arm64 /t:Run
Omezení
Ne všechny funkce iOSu jsou kompatibilní s nativní AOT. Podobně ne všechny knihovny běžně používané v iOSu jsou kompatibilní s NativeAOT. Kromě stávajících omezení nativního nasazení AOTukazuje následující seznam některá další omezení při cílení na platformy podobné iOSu:
- Použití nativní AOT je povolené pouze během nasazování aplikací (
dotnet publish
). - Ladění spravovaného kódu je podporováno pouze s Monem.
- Kompatibilita s architekturou .NET MAUI je omezená.
Kompilace AOT pro aplikace pro Android
Pro zmenšení velikosti aplikace jsou .NET a .NET MAUI aplikace, které cílí na Android, sestavovány s použitím profilovaného režimu předčasné kompilace (AOT), když jsou v režimu Release. Profilovaná kompilace AOT ovlivňuje méně metod než běžná kompilace AOT. .NET 8 představuje vlastnost <AndroidStripILAfterAOT>
, která umožňuje vyjádřit výslovný souhlas s další kompilací AOT pro aplikace pro Android, aby se zmenšila velikost aplikace ještě více.
<PropertyGroup>
<AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
</PropertyGroup>
Ve výchozím nastavení nastavení AndroidStripILAfterAOT
na true
přepíše výchozí nastavení AndroidEnableProfiledAot
, což umožňuje (téměř) všechny metody kompilované pomocí AOT oříznout. Můžete také použít profilované AOT a odstraňování IL společně tak, že explicitně nastavíte obě vlastnosti na true
:
<PropertyGroup>
<AndroidStripILAfterAOT>true</AndroidStripILAfterAOT>
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
</PropertyGroup>
Aplikace pro Windows vytvořené napříč platformami
Když vytváříte aplikace, které cílí na windows na jiných platformách než Windows, výsledný spustitelný soubor se teď aktualizuje o všechny zadané prostředky Win32 – například ikona aplikace, manifest, informace o verzi.
Dříve se aplikace musely stavět na Windows, aby tyto prostředky měly. Oprava této mezery v podpoře křížové budovy byla oblíbenou žádostí, protože to byl významný problém ovlivňující složitost infrastruktury i využití prostředků.