Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
In dit artikel worden nieuwe functies in de .NET-bibliotheken voor .NET 9 beschreven.
Base64Url
Base64 is een coderingsschema waarmee willekeurige bytes worden omgezet in tekst die bestaat uit een specifieke set van 64 tekens. Het is een algemene benadering voor het overdragen van gegevens en wordt al lang ondersteund via verschillende methoden, zoals met Convert.ToBase64String of Base64.DecodeFromUtf8(ReadOnlySpan<Byte>, Span<Byte>, Int32, Int32, Boolean). Sommige tekens die worden gebruikt, maken het echter minder ideaal voor gebruik in bepaalde omstandigheden waarin je het anders zou willen gebruiken, zoals in queryreeksen. In het bijzonder bevatten de 64 tekens uit de tabel Base64 '+' en '/', beide hebben hun eigen betekenis in URL's. Dit heeft geleid tot het maken van het Base64Url-schema, dat vergelijkbaar is met Base64, maar een iets andere set tekens gebruikt die geschikt is voor gebruik in URL-contexten. .NET 9 bevat de nieuwe Base64Url klasse, die veel nuttige en geoptimaliseerde methoden biedt voor het coderen en decoderen van Base64Url en naar verschillende gegevenstypen.
In het volgende voorbeeld ziet u hoe u de nieuwe klasse gebruikt.
ReadOnlySpan<byte> bytes = ...;
string encoded = Base64Url.EncodeToString(bytes);
BinaryFormatter
.NET 9 verwijdert BinaryFormatter uit de .NET-runtime. De API's zijn nog steeds aanwezig, maar hun implementaties genereren altijd een uitzondering, ongeacht het projecttype. Zie de handleiding voor de migratie van BinaryFormatter voor meer informatie over het verwijderen en uw opties.
Verzamelingen
De verzamelingstypen in .NET krijgen de volgende updates voor .NET 9:
- Verzamelingsopzoekingen met reeksen
OrderedDictionary<TKey, TValue>- Met de methode PriorityQueue.Remove() kunt u de prioriteit van een item in de wachtrij bijwerken.
ReadOnlySet<T>
Verzamelingszoekacties met reeksen
In high-performance code worden spans vaak gebruikt om onnodige toewijzing van tekenreeksen te voorkomen, en opzoektabellen met typen zoals Dictionary<TKey,TValue> en HashSet<T> worden vaak gebruikt als caches. Er is echter geen veilig, ingebouwd mechanisme voor het uitvoeren van zoekacties met spans op deze verzamelingstypen. Met de nieuwe allows ref struct functie in C# 13 en nieuwe functies voor deze verzamelingstypen in .NET 9 is het nu mogelijk om dit soort zoekacties uit te voeren.
Het volgende voorbeeld illustreert het gebruik van Dictionary<TKey, TValue>.GetAlternateLookup.
static Dictionary<string, int> CountWords(ReadOnlySpan<char> input)
{
Dictionary<string, int> wordCounts = new(StringComparer.OrdinalIgnoreCase);
Dictionary<string, int>.AlternateLookup<ReadOnlySpan<char>> spanLookup =
wordCounts.GetAlternateLookup<ReadOnlySpan<char>>();
foreach (Range wordRange in Regex.EnumerateSplits(input, @"\b\W+"))
{
if (wordRange.Start.Value == wordRange.End.Value)
{
continue; // Skip empty ranges.
}
ReadOnlySpan<char> word = input[wordRange];
spanLookup[word] = spanLookup.TryGetValue(word, out int count) ? count + 1 : 1;
}
return wordCounts;
}
OrderedDictionary<TKey, TValue>
In veel scenario's kunt u sleutel-waardeparen opslaan op een manier waarop volgorde kan worden onderhouden (een lijst met sleutel-waardeparen), maar waarbij snel zoeken op sleutel ook wordt ondersteund (een woordenlijst met sleutel-waardeparen). Sinds de vroege dagen van .NET heeft het OrderedDictionary type dit scenario ondersteund, maar alleen op een niet-algemene manier, waarbij sleutels en waarden zijn getypt als object. .NET 9 introduceert de lang aangevraagde OrderedDictionary<TKey,TValue> verzameling, die een efficiënt, algemeen type biedt ter ondersteuning van deze scenario's.
De volgende code maakt gebruik van de nieuwe klasse.
OrderedDictionary<string, int> d = new()
{
["a"] = 1,
["b"] = 2,
["c"] = 3,
};
d.Add("d", 4);
d.RemoveAt(0);
d.RemoveAt(2);
d.Insert(0, "e", 5);
foreach (KeyValuePair<string, int> entry in d)
{
Console.WriteLine(entry);
}
// Output:
// [e, 5]
// [b, 2]
// [c, 3]
Methode PriorityQueue.Remove()
.NET 6 heeft de PriorityQueue<TElement,TPriority> verzameling geïntroduceerd, die een eenvoudige en snelle matrix-heap-implementatie biedt. Een probleem met array-heaps in het algemeen is dat ze geen ondersteuning bieden voor prioriteitsupdates, wat hun gebruik in algoritmen zoals variaties van het algoritme van Dijkstra bemoeilijkt.
Hoewel het niet mogelijk is om efficiënte $O(\log n)$ prioriteitsupdates in de bestaande verzameling te implementeren, maakt de nieuwe PriorityQueue<TElement,TPriority>.Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) methode het mogelijk om prioriteitsupdates te emuleren (zij het op $O(n)$ tijd):
public static void UpdatePriority<TElement, TPriority>(
this PriorityQueue<TElement, TPriority> queue,
TElement element,
TPriority priority
)
{
// Scan the heap for entries matching the current element.
queue.Remove(element, out _, out _);
// Re-insert the entry with the new priority.
queue.Enqueue(element, priority);
}
Met deze methode worden gebruikers gedeblokkeerd die grafiekalgoritmen willen implementeren in contexten waarbij asymptotische prestaties geen blokkering zijn. (Dergelijke contexten omvatten onderwijs en prototyping.) Hier volgt bijvoorbeeld een eenvoudige implementatie van Dijkstra's algoritme dat gebruikmaakt van de nieuwe API.
ReadOnlySet<T>
Het is vaak wenselijk om alleen-lezenweergaven van verzamelingen te geven. ReadOnlyCollection<T> stelt u in staat een alleen-lezen wrapper rond een willekeurige veranderlijke IList<T> te maken, en met ReadOnlyDictionary<TKey,TValue> kunt u een alleen-lezen wrapper rond een willekeurige veranderlijke IDictionary<TKey,TValue> creëren. Eerdere versies van .NET hadden echter geen ingebouwde ondersteuning voor het doen van hetzelfde met ISet<T>. .NET 9 introduceert ReadOnlySet<T> om dit aan te pakken.
De nieuwe klasse maakt het volgende gebruikspatroon mogelijk.
private readonly HashSet<int> _set = [];
private ReadOnlySet<int>? _setWrapper;
public ReadOnlySet<int> Set => _setWrapper ??= new(_set);
Onderdeelmodel - TypeDescriptor ondersteuning voor bijsnijden
System.ComponentModel bevat nieuwe api's die compatibel zijn met opt-in trimmer voor het beschrijven van onderdelen. Elke toepassing, met name zelfstandige, bijgesneden toepassingen, kan deze nieuwe API's gebruiken om bijsnijdende scenario's te ondersteunen.
De primaire API is de TypeDescriptor.RegisterType methode in de TypeDescriptor klasse. Deze methode heeft het DynamicallyAccessedMembersAttribute kenmerk zodat de trimmer elementen voor dat type behoudt. U moet deze methode één keer per type aanroepen en doorgaans vroeg aanroepen.
De secundaire API's hebben een FromRegisteredType achtervoegsel, zoals TypeDescriptor.GetPropertiesFromRegisteredType(Type). In tegenstelling tot hun tegenhangers die het achtervoegsel FromRegisteredType niet hebben, hebben deze API's geen [RequiresUnreferencedCode]-kenmerken noch [DynamicallyAccessedMembers]-trimmerkenmerken. Het ontbreken van trimmerkenmerken helpt consumenten doordat ze niet langer hoeven:
- Het onderdrukken van trimming-waarschuwingen kan riskant zijn.
- Geef een sterk getypte
Typeparameter door aan andere methoden, wat lastig of onfeilbaar kan zijn.
public static void RunIt()
{
// The Type from typeof() is passed to a different method.
// The trimmer doesn't know about ExampleClass anymore
// and thus there will be warnings when trimming.
Test(typeof(ExampleClass));
Console.ReadLine();
}
private static void Test(Type type)
{
// When publishing self-contained + trimmed,
// this line produces warnings IL2026 and IL2067.
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(type);
// When publishing self-contained + trimmed,
// the property count is 0 here instead of 2.
Console.WriteLine($"Property count: {properties.Count}");
// To avoid the warning and ensure reflection
// can see the properties, register the type:
TypeDescriptor.RegisterType<ExampleClass>();
// Get properties from the registered type.
properties = TypeDescriptor.GetPropertiesFromRegisteredType(type);
Console.WriteLine($"Property count: {properties.Count}");
}
public class ExampleClass
{
public string? Property1 { get; set; }
public int Property2 { get; set; }
}
Zie het API-voorstelvoor meer informatie.
Cryptografie
- Methode CryptographicOperations.HashData()
- KMAC-algoritme
- AES-GCM en ChaChaPoly1305-algoritmen ingeschakeld voor iOS/tvOS/MacCatalyst
- X.509-certificaat laden
- Ondersteuning voor OpenSSL-providers
- Beveiliging op basis van Windows CNG-virtualisatie
Methode CryptographicOperations.HashData()
.NET bevat verschillende statische 'one-shot'- implementaties van hash-functies en gerelateerde functies. Deze API's omvatten SHA256.HashData en HMACSHA256.HashData. One-shot API's zijn te verkiezen omdat ze de best mogelijke prestaties kunnen bieden en allocaties kunnen verminderen of elimineren.
Als een ontwikkelaar een API wil bieden die ondersteuning biedt voor hashing waarbij de aanroeper definieert welk hash-algoritme moet worden gebruikt, wordt dit meestal gedaan door een HashAlgorithmName argument te accepteren. Het gebruik van dat patroon met one-shot API's vereist echter het overschakelen van elke mogelijke HashAlgorithmName en vervolgens het gebruik van de juiste methode. Om dit probleem op te lossen, introduceert .NET 9 de CryptographicOperations.HashData API. Met deze API kunt u een hash of HMAC produceren over een gegeven in één keer, waarbij het gebruikte algoritme wordt bepaald door een HashAlgorithmName.
static void HashAndProcessData(HashAlgorithmName hashAlgorithmName, byte[] data)
{
byte[] hash = CryptographicOperations.HashData(hashAlgorithmName, data);
ProcessHash(hash);
}
KMAC-algoritme
.NET 9 biedt het KMAC-algoritme zoals opgegeven door NIST SP-800-185. KECCAK Message Authentication Code (KMAC) is een pseudorandom-functie en keyed hash-functie op basis van KECCAK.
In de volgende nieuwe klassen wordt het KMAC-algoritme gebruikt. Gebruik exemplaren om gegevens te verzamelen om een MAC te produceren of gebruik de statische HashData methode voor een eenmalige opname via één invoer.
KMAC is beschikbaar in Linux met OpenSSL 3.0 of hoger en op Windows 11 Build 26016 of hoger. U kunt de statische IsSupported eigenschap gebruiken om te bepalen of het platform het gewenste algoritme ondersteunt.
if (Kmac128.IsSupported)
{
byte[] key = GetKmacKey();
byte[] input = GetInputToMac();
byte[] mac = Kmac128.HashData(key, input, outputLength: 32);
}
else
{
// Handle scenario where KMAC isn't available.
}
AES-GCM en ChaChaPoly1305-algoritmen ingeschakeld voor iOS/tvOS/MacCatalyst
IsSupported en ChaChaPoly1305.IsSupported retourneren nu true bij het draaien op iOS 13+, tvOS 13+ en Mac Catalyst.
AesGcm ondersteunt alleen tagwaarden van 16 bytes (128-bits) op Apple-besturingssystemen.
X.509-certificaat laden
Sinds .NET Framework 2.0 is de manier om een certificaat te laden new X509Certificate2(bytes). Er zijn ook andere patronen geweest, zoals new X509Certificate2(bytes, password, flags), new X509Certificate2(path), new X509Certificate2(path, password, flags)en X509Certificate2Collection.Import(bytes, password, flags) (en de overbelasting).
Deze methoden maakten gebruik van inhoudsanalyse om te bepalen of de invoer iets was dat ze konden verwerken en laadden het vervolgens als dat mogelijk was. Voor sommige bellers was deze strategie erg handig. Maar er zijn ook enkele problemen:
- Niet elke bestandsindeling werkt op elk besturingssysteem.
- Het is een protocoldeviatie.
- Het is een bron van beveiligingsproblemen.
.NET 9 introduceert een nieuwe X509CertificateLoader klasse, met een 'één methode, één doel'-ontwerp. In de oorspronkelijke versie ondersteunt het slechts twee van de vijf indelingen die door de X509Certificate2 constructor worden ondersteund. Dit zijn de twee indelingen die op alle besturingssystemen werkten.
Ondersteuning voor OpenSSL-providers
.NET 8 heeft de OpenSSL-specifieke API's OpenPrivateKeyFromEngine(String, String) en OpenPublicKeyFromEngine(String, String). Ze maken interactie mogelijk met OpenSSL-onderdelen ENGINE en gebruiken bijvoorbeeld hardwarebeveiligingsmodules (HSM).
.NET 9 introduceert SafeEvpPKeyHandle.OpenKeyFromProvider(String, String), waarmee u OpenSSL-providers kunt gebruiken en kunt communiceren met providers zoals tpm2 of pkcs11.
Sommige distributies hebben ondersteuning verwijderd ENGINE omdat deze nu is afgeschaft.
In het volgende fragment ziet u het basisgebruik:
byte[] data = [ /* example data */ ];
// Refer to your provider documentation, for example, https://github.com/tpm2-software/tpm2-openssl/tree/master.
using (SafeEvpPKeyHandle priKeyHandle = SafeEvpPKeyHandle.OpenKeyFromProvider("tpm2", "handle:0x81000007"))
using (ECDsa ecdsaPri = new ECDsaOpenSsl(priKeyHandle))
{
byte[] signature = ecdsaPri.SignData(data, HashAlgorithmName.SHA256);
// Do stuff with signature created by TPM.
}
Er zijn enkele prestatieverbeteringen tijdens de TLS-handshake en verbeteringen in interacties met RSA-persoonlijke sleutels die gebruikmaken van ENGINE onderdelen.
Beveiliging op basis van Windows CNG-virtualisatie
Windows 11 heeft nieuwe API's toegevoegd om Windows-sleutels te beveiligen met beveiliging op basis van virtualisatie (VBS). Met deze nieuwe mogelijkheid kunnen sleutels worden beveiligd tegen aanvallen op sleuteldiefstal op beheerniveau, met een te verwaarlozen effect op prestaties, betrouwbaarheid of schaal.
.NET 9 heeft overeenkomende CngKeyCreationOptions vlaggen toegevoegd. De volgende drie vlaggen zijn toegevoegd:
-
CngKeyCreationOptions.PreferVbsbijpassendNCRYPT_PREFER_VBS_FLAG -
CngKeyCreationOptions.RequireVbsbijpassendNCRYPT_REQUIRE_VBS_FLAG -
CngKeyCreationOptions.UsePerBootKeybijpassendNCRYPT_USE_PER_BOOT_KEY_FLAG
In het volgende codefragment ziet u hoe u een van de vlaggen gebruikt:
using System.Security.Cryptography;
CngKeyCreationParameters cngCreationParams = new()
{
Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider,
KeyCreationOptions = CngKeyCreationOptions.RequireVbs | CngKeyCreationOptions.OverwriteExistingKey,
};
using (CngKey key = CngKey.Create(CngAlgorithm.ECDsaP256, "myKey", cngCreationParams))
using (ECDsaCng ecdsa = new ECDsaCng(key))
{
// Do stuff with the key.
}
Datum en tijd - nieuwe TimeSpan.From* overloads
De TimeSpan klasse biedt verschillende From* methoden waarmee u een TimeSpan object kunt maken met behulp van een double. Echter, omdat double een binaire drijvendekommage-indeling is, kan inherente onnauwkeurigheid leiden tot fouten. Een mogelijk voorbeeld is dat TimeSpan.FromSeconds(101.832) niet precies 101 seconds, 832 milliseconds vertegenwoordigt, maar in plaats daarvan ongeveer 101 seconds, 831.9999999999936335370875895023345947265625 milliseconds. Deze discrepantie heeft vaak verwarring veroorzaakt en het is ook niet de meest efficiënte manier om dergelijke gegevens weer te geven. Hiervoor voegt .NET 9 nieuwe overbelastingen toe waarmee u objecten kunt maken TimeSpan op basis van gehele getallen. Er zijn nieuwe overloads van FromDays, FromHours, FromMinutes, FromSeconds, FromMilliseconds en FromMicroseconds.
De volgende code toont een voorbeeld van het aanroepen van de double en een van de nieuwe overbelastingen van gehele getallen.
TimeSpan timeSpan1 = TimeSpan.FromSeconds(value: 101.832);
Console.WriteLine($"timeSpan1 = {timeSpan1}");
// timeSpan1 = 00:01:41.8319999
TimeSpan timeSpan2 = TimeSpan.FromSeconds(seconds: 101, milliseconds: 832);
Console.WriteLine($"timeSpan2 = {timeSpan2}");
// timeSpan2 = 00:01:41.8320000
Afhankelijkheidsinjectie - ActivatorUtilities.CreateInstance constructor
De constructorresolutie is ActivatorUtilities.CreateInstance gewijzigd in .NET 9. Eerder is een constructor die expliciet is gemarkeerd met het ActivatorUtilitiesConstructorAttribute kenmerk mogelijk niet aangeroepen, afhankelijk van de volgorde van constructors en het aantal constructorparameters. De logica is gewijzigd in .NET 9, zodat een constructor met het kenmerk altijd wordt aangeroepen.
Diagnostiek
- Debug.Assert rapporteert standaard de assert-voorwaarde
- Nieuwe Activity.AddLink-methode
- Metrics.Gauge-instrument
- Out-of-proc Meter wildcard monitoren
Debug.Assert rapporteert standaard een assertieconditie
Debug.Assert wordt vaak gebruikt om voorwaarden te valideren die naar verwachting altijd waar zijn. Fout duidt meestal op een fout in de code. Er zijn veel overbelastingen van Debug.Assert, waarvan de eenvoudigste slechts een voorwaarde accepteert:
Debug.Assert(a > 0 && b > 0);
De assert mislukt als de voorwaarde onwaar is. In het verleden ontbraken dergelijke beweringen echter aan enige informatie over welke voorwaarde niet voldeed. Als er vanaf .NET 9 geen bericht expliciet wordt verstrekt door de gebruiker, bevat de assert de tekstuele weergave van de voorwaarde. Bijvoorbeeld, voor het vorige assertievoorbeeld, in plaats van een bericht te krijgen zoals:
Process terminated. Assertion failed.
at Program.SomeMethod(Int32 a, Int32 b)
Het bericht is nu:
Process terminated. Assertion failed.
a > 0 && b > 0
at Program.SomeMethod(Int32 a, Int32 b)
Nieuwe Activity.AddLink-methode
Voorheen kon u alleen een tracering Activity koppelen aan andere contexten voor tracering wanneer u de Activity maakte. Nieuw in .NET 9, kunt u met de AddLink(ActivityLink) API een Activity object koppelen aan andere traceringscontexten nadat het is gemaakt. Deze wijziging komt ook overeen met de OpenTelemetry-specificaties .
ActivityContext activityContext = new(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None);
ActivityLink activityLink = new(activityContext);
Activity activity = new("LinkTest");
activity.AddLink(activityLink);
Metrics.Gauge-instrument
System.Diagnostics.Metrics biedt nu het Gauge<T> instrument volgens de OpenTelemetry-specificatie. Het Gauge instrument is ontworpen om niet-additieve waarden vast te leggen wanneer er wijzigingen optreden. Het kan bijvoorbeeld het achtergrondruisniveau meten, waarbij het optellen van de waarden uit meerdere ruimten ongevoelig zou zijn. Het Gauge instrument is een algemeen type dat elk waardetype kan vastleggen, zoals int, doubleof decimal.
In het volgende voorbeeld ziet u hoe u het Gauge instrument gebruikt.
Meter soundMeter = new("MeasurementLibrary.Sound");
Gauge<int> gauge = soundMeter.CreateGauge<int>(
name: "NoiseLevel",
unit: "dB", // Decibels.
description: "Background Noise Level"
);
gauge.Record(10, new TagList() { { "Room1", "dB" } });
Luisteren naar Out-of-Proc Meter-wildcards
Het is al mogelijk om te luisteren naar meters die niet worden verwerkt met behulp van de System.Diagnostics.Metrics gebeurtenisbronprovider, maar vóór .NET 9 moest u de volledige meternaam opgeven. In .NET 9 kunt u naar alle meters luisteren met behulp van het jokerteken *, waarmee u metrische gegevens van elke meter in een proces kunt vastleggen. Daarnaast wordt ondersteuning toegevoegd voor luisteren per metervoorvoegsel, zodat u kunt luisteren naar alle meters waarvan de namen beginnen met een opgegeven voorvoegsel. Als u bijvoorbeeld opgeeft MyMeter* , kunt u luisteren naar alle meters met namen die beginnen met MyMeter.
// The complete meter name is "MyCompany.MyMeter".
var meter = new Meter("MyCompany.MyMeter");
// Create a counter and allow publishing values.
meter.CreateObservableCounter("MyCounter", () => 1);
// Create the listener to use the wildcard character
// to listen to all meters using prefix names.
MyEventListener listener = new MyEventListener();
De MyEventListener klasse wordt als volgt gedefinieerd.
internal class MyEventListener : EventListener
{
protected override void OnEventSourceCreated(EventSource eventSource)
{
Console.WriteLine(eventSource.Name);
if (eventSource.Name == "System.Diagnostics.Metrics")
{
// Listen to all meters with names starting with "MyCompany".
// If using "*", allow listening to all meters.
EnableEvents(
eventSource,
EventLevel.Informational,
(EventKeywords)0x3,
new Dictionary<string, string?>() { { "Metrics", "MyCompany*" } }
);
}
}
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
// Ignore other events.
if (eventData.EventSource.Name != "System.Diagnostics.Metrics" ||
eventData.EventName == "CollectionStart" ||
eventData.EventName == "CollectionStop" ||
eventData.EventName == "InstrumentPublished"
)
return;
Console.WriteLine(eventData.EventName);
if (eventData.Payload is not null)
{
for (int i = 0; i < eventData.Payload.Count; i++)
Console.WriteLine($"\t{eventData.PayloadNames![i]}: {eventData.Payload[i]}");
}
}
}
Wanneer u de code uitvoert, is de uitvoer als volgt:
CounterRateValuePublished
sessionId: 7cd94a65-0d0d-460e-9141-016bf390d522
meterName: MyCompany.MyMeter
meterVersion:
instrumentName: MyCounter
unit:
tags:
rate: 0
value: 1
instrumentId: 1
CounterRateValuePublished
sessionId: 7cd94a65-0d0d-460e-9141-016bf390d522
meterName: MyCompany.MyMeter
meterVersion:
instrumentName: MyCounter
unit:
tags:
rate: 0
value: 1
instrumentId: 1
U kunt ook het jokerteken gebruiken om te luisteren naar metrische gegevens met bewakingshulpprogramma's zoals dotnet-counters.
LINQ
Nieuwe methoden CountBy en AggregateBy zijn geïntroduceerd. Deze methoden maken het mogelijk om de status per sleutel te aggregeren zonder tussenliggende groeperingen toe te wijzen via GroupBy.
CountBy hiermee kunt u snel de frequentie van elke sleutel berekenen. In het volgende voorbeeld wordt het woord gevonden dat het vaakst voorkomt in een tekenreeks.
string sourceText = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed non risus. Suspendisse lectus tortor, dignissim sit amet,
adipiscing nec, ultricies sed, dolor. Cras elementum ultrices amet diam.
""";
// Find the most frequent word in the text.
KeyValuePair<string, int> mostFrequentWord = sourceText
.Split(new char[] { ' ', '.', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(word => word.ToLowerInvariant())
.CountBy(word => word)
.MaxBy(pair => pair.Value);
Console.WriteLine(mostFrequentWord.Key); // amet
AggregateBy hiermee kunt u meer algemene werkstromen implementeren. In het volgende voorbeeld ziet u hoe u scores kunt berekenen die zijn gekoppeld aan een bepaalde sleutel.
(string id, int score)[] data =
[
("0", 42),
("1", 5),
("2", 4),
("1", 10),
("0", 25),
];
var aggregatedData =
data.AggregateBy(
keySelector: entry => entry.id,
seed: 0,
(totalScore, curr) => totalScore + curr.score
);
foreach (var item in aggregatedData)
{
Console.WriteLine(item);
}
//(0, 67)
//(1, 15)
//(2, 4)
Index<TSource>(IEnumerable<TSource>) maakt het mogelijk om snel de impliciete index van een opsomming te extraheren. U kunt nu code schrijven, zoals het volgende codefragment, om items in een verzameling automatisch te indexeren.
IEnumerable<string> lines2 = File.ReadAllLines("output.txt");
foreach ((int index, string line) in lines2.Index())
{
Console.WriteLine($"Line number: {index + 1}, Line: {line}");
}
Brongenerator voor logboekregistratie
C# 12 heeft primaire constructors geïntroduceerd, waarmee u een constructor rechtstreeks op de klassedeclaratie kunt definiëren. De brongenerator voor logboekregistratie ondersteunt nu logboekregistratie met klassen die een primaire constructor hebben.
public partial class ClassWithPrimaryConstructor(ILogger logger)
{
[LoggerMessage(0, LogLevel.Debug, "Test.")]
public partial void Test();
}
Allerlei
In deze sectie vindt u informatie over:
allows ref struct gebruikt in bibliotheken
C# 13 introduceert de mogelijkheid om een algemene parameter te beperken met allows ref struct, waarmee de compiler en runtime wordt aangegeven dat een ref struct kan worden gebruikt voor die algemene parameter. Veel API's die compatibel zijn met dit, zijn nu voorzien van aantekeningen. De String.Create methode heeft bijvoorbeeld een overbelasting waarmee u een string bestand kunt maken door rechtstreeks in het geheugen te schrijven, weergegeven als een span. Deze methode heeft een TState argument dat door de aanroeper wordt doorgegeven aan de gemachtigde die het werkelijke schrijven doet.
Deze TState typeparameter String.Create is nu voorzien van allows ref struct:
public static string Create<TState>(int length, TState state, SpanAction<char, TState> action)
where TState : allows ref struct;
Met deze annotatie kunt u een span (of een andere ref struct) doorgeven als invoer voor deze methode.
In het volgende voorbeeld ziet u een nieuwe String.ToLowerInvariant() overbelasting die gebruikmaakt van deze mogelijkheid.
public static string ToLowerInvariant(ReadOnlySpan<char> input) =>
string.Create(span.Length, input, static (stringBuffer, input) => span.ToLowerInvariant(stringBuffer));
SearchValues expansie
.NET 8 heeft het SearchValues<T> type geïntroduceerd, dat een geoptimaliseerde oplossing biedt voor het zoeken naar specifieke sets tekens of bytes binnen spans. In .NET 9 SearchValues is uitgebreid ter ondersteuning van het zoeken naar subtekenreeksen binnen een grotere tekenreeks.
In het volgende voorbeeld wordt gezocht naar meerdere namen van dieren binnen een tekenreekswaarde en wordt een index geretourneerd naar de eerste gevonden.
private static readonly SearchValues<string> s_animals =
SearchValues.Create(["cat", "mouse", "dog", "dolphin"], StringComparison.OrdinalIgnoreCase);
public static int IndexOfAnimal(string text) =>
text.AsSpan().IndexOfAny(s_animals);
Deze nieuwe mogelijkheid heeft een geoptimaliseerde implementatie die gebruikmaakt van de SIMD-ondersteuning in het onderliggende platform. Ook kunnen typen op een hoger niveau worden geoptimaliseerd. Maakt nu bijvoorbeeld Regex gebruik van deze functionaliteit als onderdeel van de implementatie.
Netwerken
- SocketsHttpHandler is standaard in HttpClientFactory
- System.Net.ServerSentEvents
- TLS opnieuw starten met clientcertificaten op Linux
- WebSocket actiefhoud ping en time-out
- HttpClientFactory registreert standaard geen headerwaarden meer
SocketsHttpHandler is standaard in HttpClientFactory
HttpClientFactory maakt standaard HttpClient objecten die worden ondersteund door HttpClientHandler.
HttpClientHandler wordt zelf ondersteund door SocketsHttpHandler, wat veel meer configureerbaar is, inclusief het beheer van de levensduur van verbindingen.
HttpClientFactory wordt nu standaard gebruikt voor SocketsHttpHandler en configureert het om limieten in te stellen voor de verbindingsduur, zodat deze overeenkomen met de rotatieduur die in de fabriek is gespecificeerd.
System.Net.ServerSentEvents
Server-verzonden gebeurtenissen (SSE) is een eenvoudig en populair protocol voor het streamen van gegevens van een server naar een client. Het wordt bijvoorbeeld door OpenAI gebruikt als onderdeel van het streamen van gegenereerde tekst van de AI-diensten. Om het verbruik van SSE te vereenvoudigen, biedt de nieuwe System.Net.ServerSentEvents bibliotheek een parser voor het eenvoudig opnemen van door de server verzonden gebeurtenissen.
De volgende code laat zien hoe u de nieuwe klasse gebruikt.
Stream responseStream = new MemoryStream();
await foreach (SseItem<string> e in SseParser.Create(responseStream).EnumerateAsync())
{
Console.WriteLine(e.Data);
}
TLS hervatten met clientcertificaten op Linux
TLS hervatten is een functie van het TLS-protocol waarmee eerder tot stand gebrachte sessies naar een server kunnen worden hervat. Door dit te doen worden onnodige netwerktransacties vermeden en worden computatiemiddelen bespaard tijdens de TLS-handshake.
TLS resume wordt al ondersteund op Linux voor SslStream-verbindingen zonder clientcertificaten. .NET 9 voegt ondersteuning toe voor tls-hervatten van wederzijds geverifieerde TLS-verbindingen, die gebruikelijk zijn in server-naar-serverscenario's. De functie wordt automatisch ingeschakeld.
WebSocket keep-alive ping en tijdslimiet
Nieuwe API's op ClientWebSocketOptions en WebSocketCreationOptions stellen u in staat om ervoor te kiezen WebSocket pings te verzenden en de verbinding te verbreken als de peer niet op tijd reageert.
Tot nu toe kunt u een KeepAliveInterval opgeven om ervoor te zorgen dat de verbinding niet actief blijft, maar er is geen ingebouwd mechanisme om af te dwingen dat de peer reageert.
In het volgende voorbeeld wordt de server elke vijf seconden pingt en wordt de verbinding afgebroken als deze niet binnen een seconde reageert.
using var cws = new ClientWebSocket();
cws.Options.HttpVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;
cws.Options.KeepAliveInterval = TimeSpan.FromSeconds(5);
cws.Options.KeepAliveTimeout = TimeSpan.FromSeconds(1);
await cws.ConnectAsync(uri, httpClient, cancellationToken);
HttpClientFactory registreert standaard geen headerwaarden meer
LogLevel.Trace gebeurtenissen die door HttpClientFactory zijn geregistreerd, bevatten standaard geen headerwaarden meer. U kunt kiezen voor het registreren van waarden voor specifieke headers via de RedactLoggedHeaders helpermethode.
In het volgende voorbeeld worden alle headers herwerkt, met uitzondering van de gebruikersagent.
services.AddHttpClient("myClient")
.RedactLoggedHeaders(name => name != "User-Agent");
Zie HttpClientFactory logging standaard de headerwaarden onleesbaar maakt voor meer informatie.
Reflectie
Persistente assemblies
In .NET Core-versies en .NET 5-8 was de ondersteuning voor het bouwen van een assembly en het verzenden van weerspiegelingsmetagegevens voor dynamisch gemaakte typen beperkt tot een uitvoerbaar AssemblyBuilder. Het gebrek aan ondersteuning voor het opslaan van een assembly was vaak een obstakel voor klanten die migreren van .NET Framework naar .NET. .NET 9 voegt een nieuw type toe, PersistedAssemblyBuilderdat u kunt gebruiken om een verzonden assembly op te slaan.
Als u een PersistedAssemblyBuilder-exemplaar wilt maken, roept u de constructor aan en geeft u de assemblynaam, de kernassembly, System.Private.CoreLib, door om basale runtimetypen te refereren en eventueel aangepaste kenmerken mee te geven. Nadat u alle leden naar de assembly hebt verzonden, roept u de PersistedAssemblyBuilder.Save(String) methode aan om een assembly met standaardinstellingen te maken. Als u het toegangspunt of andere opties wilt instellen, kunt u PersistedAssemblyBuilder.GenerateMetadata aanroepen en de geretourneerde metagegevens gebruiken om de assembly op te slaan. De volgende code toont een voorbeeld van het maken van een persistente assembly en het instellen van het toegangspunt.
public void CreateAndSaveAssembly(string assemblyPath)
{
PersistedAssemblyBuilder ab = new PersistedAssemblyBuilder(
new AssemblyName("MyAssembly"),
typeof(object).Assembly
);
TypeBuilder tb = ab.DefineDynamicModule("MyModule")
.DefineType("MyType", TypeAttributes.Public | TypeAttributes.Class);
MethodBuilder entryPoint = tb.DefineMethod(
"Main",
MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static
);
ILGenerator il = entryPoint.GetILGenerator();
// ...
il.Emit(OpCodes.Ret);
tb.CreateType();
MetadataBuilder metadataBuilder = ab.GenerateMetadata(
out BlobBuilder ilStream,
out BlobBuilder fieldData
);
PEHeaderBuilder peHeaderBuilder = new PEHeaderBuilder(
imageCharacteristics: Characteristics.ExecutableImage);
ManagedPEBuilder peBuilder = new ManagedPEBuilder(
header: peHeaderBuilder,
metadataRootBuilder: new MetadataRootBuilder(metadataBuilder),
ilStream: ilStream,
mappedFieldData: fieldData,
entryPoint: MetadataTokens.MethodDefinitionHandle(entryPoint.MetadataToken)
);
BlobBuilder peBlob = new BlobBuilder();
peBuilder.Serialize(peBlob);
using var fileStream = new FileStream("MyAssembly.exe", FileMode.Create, FileAccess.Write);
peBlob.WriteContentTo(fileStream);
}
public static void UseAssembly(string assemblyPath)
{
Assembly assembly = Assembly.LoadFrom(assemblyPath);
Type? type = assembly.GetType("MyType");
MethodInfo? method = type?.GetMethod("SumMethod");
Console.WriteLine(method?.Invoke(null, [5, 10]));
}
De nieuwe PersistedAssemblyBuilder klasse bevat PDB-ondersteuning. U kunt symboolgegevens verzenden en deze gebruiken om fouten op te sporen in een gegenereerde assembly. De API heeft een vergelijkbare vorm als de .NET Framework-implementatie. Zie Symbolen verzenden en PDB genereren voor meer informatie.
Typenaam parseren
TypeName is een parser voor ECMA-335-typenamen die veel dezelfde functionaliteit bieden als System.Type die is losgekoppeld van de runtime-omgeving. Onderdelen zoals serializers en compilers moeten namen van typetypen parseren en verwerken. De systeemeigen AOT-compiler is bijvoorbeeld overgeschakeld naar het gebruik van TypeName.
De nieuwe TypeName klasse biedt:
Statisch
ParseenTryParsemethoden voor het parseren van invoer weergegeven alsReadOnlySpan<char>. Beide methoden accepteren een exemplaar vanTypeNameParseOptionsklasse (een optietas) waarmee u de parsering kunt aanpassen.Name,FullNameenAssemblyQualifiedNameeigenschappen die precies zo werken als hun tegenhangers in System.Type.Meerdere eigenschappen en methoden die aanvullende informatie geven over de naam zelf:
-
IsArray,IsSZArray(SZstaat voor één dimensie, nul-geïndexeerde matrix),IsVariableBoundArrayTypeenGetArrayRankvoor het werken met matrices. -
IsConstructedGenericType,GetGenericTypeDefinitionenGetGenericArgumentsvoor het werken met algemene typenamen. -
IsByRefenIsPointervoor het werken met aanwijzers en beheerde verwijzingen. -
GetElementType()voor het werken met aanwijzers, verwijzingen en matrices. -
IsNestedenDeclaringTypevoor het werken met geneste typen. -
AssemblyName, waarmee de informatie over de assemblynaam beschikbaar wordt gesteld via de nieuwe AssemblyNameInfo klasse. In tegenstelling totAssemblyName, het nieuwe type is onveranderbaar en het parseren van cultuurnamen maakt geen exemplaren vanCultureInfo.
-
Beide TypeName typen AssemblyNameInfo zijn onveranderbaar en bieden geen manier om te controleren op gelijkheid (ze implementeren IEquatableniet). Het vergelijken van assemblynamen is eenvoudig, maar verschillende scenario's moeten alleen een subset van weergegeven informatie (Name, Version, CultureName, en PublicKeyOrToken) vergelijken.
In het volgende codefragment ziet u een voorbeeldgebruik.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
internal class RestrictedSerializationBinder
{
Dictionary<string, Type> AllowList { get; set; }
RestrictedSerializationBinder(Type[] allowedTypes)
=> AllowList = allowedTypes.ToDictionary(type => type.FullName!);
Type? GetType(ReadOnlySpan<char> untrustedInput)
{
if (!TypeName.TryParse(untrustedInput, out TypeName? parsed))
{
throw new InvalidOperationException($"Invalid type name: '{untrustedInput.ToString()}'");
}
if (AllowList.TryGetValue(parsed.FullName, out Type? type))
{
return type;
}
else if (parsed.IsSimple // It's not generic, pointer, reference, or an array.
&& parsed.AssemblyName is not null
&& parsed.AssemblyName.Name == "MyTrustedAssembly"
)
{
return Type.GetType(parsed.AssemblyQualifiedName, throwOnError: true);
}
throw new InvalidOperationException($"Not allowed: '{untrustedInput.ToString()}'");
}
}
De nieuwe API's zijn beschikbaar in het System.Reflection.Metadata NuGet-pakket, dat kan worden gebruikt met .NET-versies van lager niveau.
Reguliere expressies
[GeneratedRegex] op eigenschappen
.NET 7 heeft de brongenerator en het Regex bijbehorende GeneratedRegexAttribute kenmerk geïntroduceerd.
De volgende gedeeltelijke methode wordt gegenereerd met alle code, die nodig is om Regex te implementeren.
[GeneratedRegex(@"\b\w{5}\b")]
private static partial Regex FiveCharWord();
C# 13 biedt ondersteuning voor gedeeltelijke eigenschappen naast gedeeltelijke methoden, dus vanaf .NET 9 kunt u ook op een eigenschap gebruiken [GeneratedRegex(...)] .
De volgende gedeeltelijke eigenschap is het eigenschapsequivalent van het vorige voorbeeld.
[GeneratedRegex(@"\b\w{5}\b")]
private static partial Regex FiveCharWordProperty { get; }
Regex.EnumerateSplits
De Regex klasse biedt een Split methode die vergelijkbaar is met de String.Split methode. Met String.Split, geeft u een of meer char of string scheidingstekens op, en de implementatie splitst de invoertekst bij die scheidingstekens. In Regex.Split plaats van het scheidingsteken op te geven als een char of string, wordt het opgegeven als een normale expressiepatroon.
In het volgende voorbeeld ziet u hoe u dit kunt doen Regex.Split.
foreach (string s in Regex.Split("Hello, world! How are you?", "[aeiou]"))
{
Console.WriteLine($"Split: \"{s}\"");
}
// Output, split by all English vowels:
// Split: "H"
// Split: "ll"
// Split: ", w"
// Split: "rld! H"
// Split: "w "
// Split: "r"
// Split: " y"
// Split: ""
// Split: "?"
Regex.Split Accepteert echter alleen een string als invoer en biedt geen ondersteuning voor invoer die wordt geleverd als een ReadOnlySpan<char>. Ook geeft het de volledige set van splitsingen als een string[] weer, waarvoor zowel een string array moet worden gereserveerd om de resultaten vast te houden als een string voor elke splitsing. In .NET 9 maakt de nieuwe EnumerateSplits methode dezelfde bewerking mogelijk, maar met een op span gebaseerde invoer en zonder toewijzing voor de resultaten. Het accepteert een ReadOnlySpan<char> en retourneert een opsomming van Range objecten die de resultaten vertegenwoordigen.
In het volgende voorbeeld ziet u Regex.EnumerateSplits, dat een ReadOnlySpan<char> als invoer neemt.
ReadOnlySpan<char> input = "Hello, world! How are you?";
foreach (Range r in Regex.EnumerateSplits(input, "[aeiou]"))
{
Console.WriteLine($"Split: \"{input[r]}\"");
}
Serialisatie (System.Text.Json)
- Opties voor inspringing
- Singleton voor standaard webopties
- JsonSchemaExporteur
- Nulleerbare annotaties respecteren
- Vereis niet-optionele constructorparameters
- Eigenschappen van JsonObject bestellen
- Namen van enum-leden aanpassen
- Meerdere JSON-documenten streamen
Opties voor inspringing
JsonSerializerOptions bevat nieuwe eigenschappen waarmee u het inspringingsteken en de inspringingsgrootte van geschreven JSON kunt aanpassen.
var options = new JsonSerializerOptions
{
WriteIndented = true,
IndentCharacter = '\t',
IndentSize = 2,
};
string json = JsonSerializer.Serialize(
new { Value = 1 },
options
);
Console.WriteLine(json);
//{
// "Value": 1
//}
Standaardwebopties singleton
Als u wilt serialiseren met de standaardopties die ASP.NET Core gebruikt voor web-apps, gebruikt u de nieuwe JsonSerializerOptions.Web singleton.
string webJson = JsonSerializer.Serialize(
new { SomeValue = 42 },
JsonSerializerOptions.Web // Defaults to camelCase naming policy.
);
Console.WriteLine(webJson);
// {"someValue":42}
JsonSchemaExporteur
JSON wordt vaak gebruikt om typen in methodehandtekeningen weer te geven als onderdeel van externe procedure-aanroepende schema's. Het wordt bijvoorbeeld gebruikt als onderdeel van OpenAPI-specificaties of als onderdeel van het aanroepen van hulpprogramma's met AI-services zoals die van OpenAI. Ontwikkelaars kunnen .NET-typen serialiseren en deserialiseren als JSON met behulp van System.Text.Json. Maar ze moeten ook een JSON-schema kunnen ophalen dat de vorm van het .NET-type beschrijft (dat wil gezegd, beschrijft de vorm van wat zou worden geserialiseerd en wat kan worden gedeserialiseerd). System.Text.Json biedt nu het type, dat ondersteuning biedt voor het JsonSchemaExporter genereren van een JSON-schema dat een .NET-type vertegenwoordigt.
Zie JSON-schemaexporteur voor meer informatie.
Respecteer nulwaardeaantekeningen
System.Text.Json herkent nu null-annotaties van eigenschappen en kan worden geconfigureerd om deze af te dwingen tijdens serialisatie en deserialisatie met behulp van de RespectNullableAnnotations vlag.
De volgende code laat zien hoe u de optie instelt:
public static void RunIt()
{
JsonSerializerOptions options = new() { RespectNullableAnnotations = true };
// Throws exception: System.Text.Json.JsonException: The property or field
// 'Title' on type 'Serialization+Book' doesn't allow getting null values.
// Consider updating its nullability annotation.
JsonSerializer.Serialize(new Book { Title = null! }, options);
// Throws exception: System.Text.Json.JsonException: The property or field
// 'Title' on type 'Serialization+Book' doesn't allow setting null values.
// Consider updating its nullability annotation.
JsonSerializer.Deserialize<Book>("""{ "Title" : null }""", options);
}
public class Book
{
public required string Title { get; set; }
public string? Author { get; set; }
public int PublishYear { get; set; }
}
Zie Nullable annotaties respecteren voor meer informatie.
Niet-optionele constructorparameters vereisen
Historisch gezien heeft System.Text.Json niet-optionele constructorparameters als optioneel behandeld bij het gebruik van op constructoren gebaseerde deserialisatie. U kunt dit gedrag wijzigen met behulp van de nieuwe RespectRequiredConstructorParameters vlag.
De volgende code laat zien hoe u de optie instelt:
JsonSerializerOptions options = new() { RespectRequiredConstructorParameters = true };
// Throws exception: System.Text.Json.JsonException: JSON deserialization
// for type 'Serialization+MyPoco' was missing required properties including: 'Value'.
JsonSerializer.Deserialize<MyPoco>("""{}""", options);
Het MyPoco type wordt als volgt gedefinieerd:
record MyPoco(string Value);
Zie Niet-optionele constructorparameters voor meer informatie.
Eigenschappen van JsonObject bestellen
Het JsonObject type stelt nu geordende dictionary-achtige APIs beschikbaar waarmee expliciete manipulatie van de volgorde van eigenschappen mogelijk is.
JsonObject jObj = new()
{
["key1"] = true,
["key3"] = 3
};
Console.WriteLine(jObj is IList<KeyValuePair<string, JsonNode?>>); // True.
// Insert a new key-value pair at the correct position.
int key3Pos = jObj.IndexOf("key3") is int i and >= 0 ? i : 0;
jObj.Insert(key3Pos, "key2", "two");
foreach (KeyValuePair<string, JsonNode?> item in jObj)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
// Output:
// key1: true
// key2: two
// key3: 3
Zie Eigenschapsvolgorde bewerken voor meer informatie.
Namen van enum-leden aanpassen
Het nieuwe System.Text.Json.Serialization.JsonStringEnumMemberNameAttribute kenmerk kan worden gebruikt om de namen van afzonderlijke enumleden aan te passen voor typen die als tekenreeksen worden geserialiseerd:
JsonSerializer.Serialize(MyEnum.Value1 | MyEnum.Value2); // "Value1, Custom enum value"
[Flags, JsonConverter(typeof(JsonStringEnumConverter))]
enum MyEnum
{
Value1 = 1,
[JsonStringEnumMemberName("Custom enum value")]
Value2 = 2,
}
Zie Aangepaste namen van leden voor opsommingen voor meer informatie.
Meerdere JSON-documenten streamen
System.Text.Json.Utf8JsonReader ondersteunt nu het lezen van meerdere, witruimte gescheiden JSON-documenten uit één buffer of stream. Standaard genereert de lezer een uitzondering als er niet-witruimtetekens worden gedetecteerd die het eerste document op het hoogste niveau volgen. U kunt dit gedrag wijzigen met behulp van de AllowMultipleValues vlag.
Zie Meerdere JSON-documenten lezen voor meer informatie.
Overspanningen
In code met hoge prestaties worden spans vaak gebruikt om te voorkomen dat tekenreeksen onnodig worden toegewezen. Span<T> en ReadOnlySpan<T> blijven de manier waarop code wordt geschreven in .NET revolutioneren, en bij elke release worden er steeds meer methodes toegevoegd die op spans werken. .NET 9 bevat de volgende aan span gerelateerde updates:
- Helpers voor bestanden
-
params ReadOnlySpan<T>overbelasting - Itereer over de segmenten van ReadOnlySpan<char>.Split()
Helpers voor bestanden
De File klasse heeft nu nieuwe hulpmiddelen om eenvoudig en rechtstreeks ReadOnlySpan<char>/ReadOnlySpan<byte> en ReadOnlyMemory<char>/ReadOnlyMemory<byte> naar bestanden te schrijven.
De volgende code schrijft efficiënt een ReadOnlySpan<char> naar een bestand.
ReadOnlySpan<char> text = ...;
File.WriteAllText(filePath, text);
Er zijn ook nieuwe StartsWith<T>(ReadOnlySpan<T>, T) en EndsWith<T>(ReadOnlySpan<T>, T) uitbreidingsmethoden toegevoegd voor spans, zodat u eenvoudig kunt testen of een ReadOnlySpan<T> begin of eindigt met een specifieke T waarde.
De volgende code maakt gebruik van deze nieuwe handige API's.
ReadOnlySpan<char> text = "some arbitrary text";
return text.StartsWith('"') && text.EndsWith('"'); // false
params ReadOnlySpan<T> overbelasting
C# heeft altijd ondersteuning geboden voor het markeren van matrixparameters als params. Dit trefwoord maakt een vereenvoudigde aanroepsyntaxis mogelijk. De tweede parameter van de String.Join(String, String[]) methode wordt bijvoorbeeld gemarkeerd met params. U kunt deze overbelasting aanroepen met een matrix of door de waarden afzonderlijk door te geven:
string result = string.Join(", ", new string[3] { "a", "b", "c" });
string result = string.Join(", ", "a", "b", "c");
Voordat u .NET 9 de waarden afzonderlijk doorgeeft, verzendt de C#-compiler code die identiek is aan de eerste aanroep door een impliciete matrix rond de drie argumenten te produceren.
Vanaf C# 13 kunt u params gebruiken met elk argument dat kan worden samengesteld via een verzamelingsexpressie, inclusief spans (Span<T> en ReadOnlySpan<T>). Dat is nuttig voor bruikbaarheid en prestaties. De C#-compiler kan de argumenten op de stack opslaan, een span eromheen verpakken en deze doorgeven aan de methode, waardoor de impliciete matrixtoewijzing wordt vermeden die anders zou hebben geresulteerd.
.NET 9 bevat meer dan 60 methoden met een params ReadOnlySpan<T> parameter. Sommige zijn gloednieuwe overloads en sommige zijn bestaande methoden die al een ReadOnlySpan<T> hadden, maar nu is die parameter gemarkeerd met params. Het netto-effect is als u een upgrade uitvoert naar .NET 9 en uw code opnieuw compileert, ziet u prestatieverbeteringen zonder codewijzigingen aan te brengen. Dat komt omdat de compiler liever bindt aan overbelastingen op basis van span dan aan de op matrix gebaseerde overbelastingen.
Bevat nu bijvoorbeeld String.Join de volgende overbelasting, waarmee het nieuwe patroon wordt geïmplementeerd: String.Join(String, ReadOnlySpan<String>)
Er wordt nu een aanroep string.Join(", ", "a", "b", "c") uitgevoerd zonder een array toe te wijzen om de "a", "b" en "c" argumenten door te geven.
Opsommen over ReadOnlySpan<char>.Split()-segmenten
string.Split is een handige methode voor het snel partitioneren van een tekenreeks met een of meer opgegeven scheidingstekens. Voor code die is gericht op prestaties, kan het toewijzingsprofiel string.Split echter verboden zijn, omdat er een tekenreeks wordt toegewezen voor elk geparseerd onderdeel en een string[] om ze allemaal op te slaan. Het werkt ook niet met spans, dus als u een ReadOnlySpan<char> tekenreeks hebt, moet u nog een tekenreeks toewijzen wanneer u deze converteert naar een tekenreeks, zodat u vervolgens string.Split erop kunt aanroepen.
In .NET 8 zijn een set Split en SplitAny methoden geïntroduceerd voor ReadOnlySpan<char>. In plaats van een nieuwe string[]te retourneren, accepteren deze methoden een bestemming Span<Range> waarin de begrenzingsindexen voor elk onderdeel worden geschreven. Hierdoor is de bewerking volledig zonder toewijzing. Deze methoden zijn geschikt wanneer het aantal bereik zowel bekend als klein is.
In .NET 9 zijn nieuwe overbelastingen van Split en SplitAny toegevoegd om incrementeel parseren van een ReadOnlySpan<T> met een priori onbekend aantal segmenten toe te staan. Met de nieuwe methoden kunt u elk segment opsommen, dat op dezelfde manier wordt weergegeven als een Range segment dat kan worden gebruikt om in het oorspronkelijke bereik te segmenteren.
public static bool ListContainsItem(ReadOnlySpan<char> span, string item)
{
foreach (Range segment in span.Split(','))
{
if (span[segment].SequenceEquals(item))
{
return true;
}
}
return false;
}
Systeem.formaten
De positie of verschuiving van de gegevens in de omsluitstroom voor een TarEntry object is nu een openbare eigenschap. TarEntry.DataOffset retourneert de positie in de archiefstroom van de vermelding waar de eerste gegevens-byte van de vermelding zich bevindt. De gegevens van de vermelding worden ingekapseld in een substroom waartoe u toegang hebt via TarEntry.DataStream, waardoor de werkelijke positie van de gegevens in verhouding tot de archiefstroom wordt verborgen. Dat is voldoende voor de meeste gebruikers, maar als u meer flexibiliteit nodig hebt en de werkelijke beginpositie van de gegevens in de archiefstroom wilt weten, maakt de nieuwe TarEntry.DataOffset API het eenvoudig om functies zoals gelijktijdige toegang met zeer grote TAR-bestanden te ondersteunen.
// Create stream for tar ball data in Azure Blob Storage.
BlobClient blobClient = new(connectionString, blobContainerName, blobName);
Stream blobClientStream = await blobClient.OpenReadAsync(options, cancellationToken);
// Create TarReader for the stream and get a TarEntry.
TarReader tarReader = new(blobClientStream);
System.Formats.Tar.TarEntry? tarEntry = await tarReader.GetNextEntryAsync();
if (tarEntry is null)
return;
// Get position of TarEntry data in blob stream.
long entryOffsetInBlobStream = tarEntry.DataOffset;
long entryLength = tarEntry.Length;
// Create a separate stream.
Stream newBlobClientStream = await blobClient.OpenReadAsync(options, cancellationToken);
newBlobClientStream.Seek(entryOffsetInBlobStream, SeekOrigin.Begin);
// Read tar ball content from separate BlobClient stream.
byte[] bytes = new byte[entryLength];
await newBlobClientStream.ReadExactlyAsync(bytes, 0, (int)entryLength);
System.Guid
NewGuid() creëert een Guid-object gevuld met cryptografisch veilige willekeurige gegevens, volgens de UUID-versie 4-specificatie in RFC 9562. Dezelfde RFC definieert ook andere versies, waaronder versie 7, die "een tijdgeordend waardeveld bevat dat is afgeleid van de algemeen geïmplementeerde en bekende Unix Epoch-tijdstempelbron". Met andere woorden, veel van de gegevens zijn nog steeds willekeurig, maar een deel ervan is gereserveerd voor gegevens op basis van een tijdstempel, waardoor deze waarden een natuurlijke sorteervolgorde kunnen hebben. In .NET 9 kunt u een Guid maken op basis van versie 7 via de nieuwe Guid.CreateVersion7() en Guid.CreateVersion7(DateTimeOffset) methoden. U kunt ook de nieuwe Version eigenschap gebruiken om het versieveld van een Guid object op te halen.
System.IO
- Compressie met zlib-ng
- Opties voor ZLib- en Brotli-compressie
- XPS-documenten van virtuele XPS-printer
Compressie met behulp van zlib-ng
System.IO.Compressionfuncties zoals ZipArchive, DeflateStream, en GZipStreamZLibStream zijn allemaal gebaseerd op de zlib-bibliotheek. Vanaf .NET 9 gebruiken deze functies allemaal zlib-ng, een bibliotheek die consistentere en efficiëntere verwerking oplevert voor een breder scala aan besturingssystemen en hardware.
Opties voor ZLib- en Brotli-compressie
ZLibCompressionOptionsen BrotliCompressionOptions zijn nieuwe typen voor het instellen van algoritmespecifiek compressieniveau en strategie (Default, FilteredHuffmanOnly, , RunLengthEncodingof Fixed). Deze typen zijn gericht op gebruikers die meer verfijnde instellingen willen dan de enige bestaande optie System.IO.Compression.CompressionLevel<>.
De nieuwe typen compressieopties kunnen in de toekomst worden uitgebreid.
In het volgende codefragment ziet u een voorbeeld van gebruik:
private MemoryStream CompressStream(Stream uncompressedStream)
{
MemoryStream compressorOutput = new();
using ZLibStream compressionStream = new(
compressorOutput,
new ZLibCompressionOptions()
{
CompressionLevel = 6,
CompressionStrategy = ZLibCompressionStrategy.HuffmanOnly
}
);
uncompressedStream.CopyTo(compressionStream);
compressionStream.Flush();
return compressorOutput;
}
XPS-documenten van virtuele XPS-printer
XPS-documenten die afkomstig zijn van een virtuele V4 XPS-printer konden eerder niet worden geopend met behulp van de System.IO.Packaging bibliotheek, vanwege gebrek aan ondersteuning voor het verwerken van .piece-bestanden . Deze kloof is opgelost in .NET 9.
Systeem.Numeriek
Bovengrens bigInteger
BigInteger ondersteunt het vertegenwoordigen van gehele getallen van in wezen willekeurige lengte. In de praktijk wordt de lengte echter beperkt door de limieten van de onderliggende computer, zoals het beschikbare geheugen of hoe lang het duurt om een bepaalde expressie te berekenen. Daarnaast bestaan er enkele API's die mislukken bij invoer die resulteren in een waarde die te groot is. Vanwege deze limieten dwingt .NET 9 een maximale lengte van BigInteger, wat inhoudt dat het niet meer dan (2^31) - 1 (ongeveer 2,14 miljard) bits kan bevatten. Een dergelijk getal vertegenwoordigt een toewijzing van bijna 256 MB en bevat ongeveer 646,5 miljoen cijfers. Deze nieuwe limiet zorgt ervoor dat alle blootgestelde API's goed functioneren en consistent zijn, terwijl nog steeds cijfers worden toegestaan die ver buiten de meeste gebruiksscenario's gaan.
BigMul API's
BigMul is een bewerking die het volledige product van twee getallen produceert. .NET 9 voegt toegewezen BigMul API's toe aanint, longen uintulong waarvan het retourtype het volgende grotere gehele getal is dan de parametertypen.
De nieuwe API's zijn:
-
BigMul(Int32, Int32) (retourneert
long) -
BigMul(Int64, Int64) (retourneert
Int128) -
BigMul(UInt32, UInt32) (retourneert
ulong) -
BigMul(UInt64, UInt64) (retourneert
UInt128)
Vectorconversie-API's
.NET 9 voegt toegewezen extensie-API's toe voor het converteren tussen Vector2, Vector3, Vector4, en Quaternion.Plane
De nieuwe API's zijn als volgt:
- AsPlane(Vector4)
- AsQuaternion(Vector4)
- AsVector2(Vector4)
- AsVector3(Vector4)
- AsVector4(Plane)
- AsVector4(Quaternion)
- AsVector4(Vector2)
- AsVector4(Vector3)
- AsVector4Unsafe(Vector2)
- AsVector4Unsafe(Vector3)
Voor conversies met dezelfde grootte, zoals tussen Vector4, Quaternion en Plane, zijn deze conversies kosteloos. Hetzelfde kan worden gezegd voor het beperken van conversies, zoals van Vector4 naar Vector2 of Vector3. Voor het breder maken van conversies, zoals van Vector2 of Vector3 naar Vector4, is er de normale API, waarmee nieuwe elementen worden geïnitialiseerd naar 0 en een Unsafe achtervoegsel-API die deze nieuwe elementen ongedefinieerd laat en daarom geen kosten kan zijn.
API's voor het maken van vectoren
Er zijn nieuwe Create API's beschikbaar voor Vector, Vector2, Vector3 en Vector4 die overeenkomen met de equivalente API's die beschikbaar zijn voor de hardwarevectortypen in de System.Runtime.Intrinsics naamruimte.
Zie voor meer informatie over de nieuwe API's:
Deze API's zijn voornamelijk bedoeld voor het gemak en de algehele consistentie van .NET's SIMD-versnelde typen.
Aanvullende acceleratie
Er zijn extra prestatieverbeteringen aangebracht in veel typen in de System.Numerics naamruimte, waaronder BigInteger, Vector2, Vector3, Vector4, Quaternion, en Plane.
In sommige gevallen heeft dit geresulteerd in een 2-5x versnelling van kern-API's, waaronder Matrix4x4 vermenigvuldiging, het maken van een Plane uit een reeks hoekpunten, Quaternion samenvoeging en het berekenen van het kruisproduct van een Vector3.
Er is ook ondersteuning voor constant folding voor de SinCos API, die zowel Sin(x) als Cos(x) in één aanroep berekent, waardoor het efficiënter is.
Tensoren voor kunstmatige intelligentie
Tensors zijn de hoeksteengegevensstructuur van kunstmatige intelligentie (AI). Ze kunnen vaak worden beschouwd als multidimensionale matrices.
Tensors worden gebruikt om:
- Gegevens weergeven en coderen, zoals tekstreeksen (tokens), afbeeldingen, video en audio.
- Efficiëntere manipulatie van hogere dimensionale gegevens.
- Efficiënt berekeningen toepassen op hogere dimensionale gegevens.
- Sla gewichtsinformatie en tussenliggende berekeningen (in neurale netwerken) op.
Als u de .NET tensor-API's wilt gebruiken, installeert u het NuGet-pakket System.Numerics.Tensors .
Nieuw Tensor<T>-type
Het nieuwe Tensor<T> type breidt de AI-mogelijkheden van de .NET-bibliotheken en runtime uit. Dit type:
- Biedt efficiënte interoperabiliteit met AI-bibliotheken zoals ML.NET, TorchSharp en ONNX Runtime met behulp van nul kopieën, waar mogelijk.
- Bouwt voort op TensorPrimitives voor efficiënte wiskundige bewerkingen.
- Maakt eenvoudige en efficiënte gegevensmanipulatie mogelijk door indexerings- en segmenteringsbewerkingen te bieden.
- Is geen vervanging voor bestaande AI- en machine learning-bibliotheken. In plaats daarvan is het bedoeld om een gemeenschappelijke set API's te bieden om codeduplicatie en afhankelijkheden te verminderen en betere prestaties te bereiken met behulp van de nieuwste runtimefuncties.
In de volgende codes ziet u enkele API's die bij het nieuwe Tensor<T> type zijn opgenomen.
// Create a tensor (1 x 3).
Tensor<int> t0 = Tensor.Create([1, 2, 3], [1, 3]); // [[1, 2, 3]]
// Reshape tensor (3 x 1).
Tensor<int> t1 = t0.Reshape(3, 1); // [[1], [2], [3]]
// Slice tensor (2 x 1).
Tensor<int> t2 = t1.Slice(1.., ..); // [[2], [3]]
// Broadcast tensor (3 x 1) -> (3 x 3).
// [
// [ 1, 1, 1],
// [ 2, 2, 2],
// [ 3, 3, 3]
// ]
var t3 = Tensor.Broadcast<int>(t1, [3, 3]);
// Math operations.
var t4 = Tensor.Add(t0, 1); // [[2, 3, 4]]
var t5 = Tensor.Add(t0.AsReadOnlyTensorSpan(), t0); // [[2, 4, 6]]
var t6 = Tensor.Subtract(t0, 1); // [[0, 1, 2]]
var t7 = Tensor.Subtract(t0.AsReadOnlyTensorSpan(), t0); // [[0, 0, 0]]
var t8 = Tensor.Multiply(t0, 2); // [[2, 4, 6]]
var t9 = Tensor.Multiply(t0.AsReadOnlyTensorSpan(), t0); // [[1, 4, 9]]
var t10 = Tensor.Divide(t0, 2); // [[0.5, 1, 1.5]]
var t11 = Tensor.Divide(t0.AsReadOnlyTensorSpan(), t0); // [[1, 1, 1]]
Opmerking
Deze API is gemarkeerd als experimenteel voor .NET 9.
TensorPrimitieven
De System.Numerics.Tensors bibliotheek bevat de TensorPrimitives klasse, die statische methoden biedt voor het uitvoeren van numerieke bewerkingen op meerdere waarden. In .NET 9 is het bereik van methoden die door TensorPrimitives beschikbaar gemaakt worden aanzienlijk uitgebreid, met een groei van 40 (in .NET 8) tot bijna 200 methodes. Het oppervlak omvat bekende numerieke bewerkingen van typen zoals Math en MathF. Het bevat ook de algemene wiskundige interfaces, zoals INumber<TSelf>, behalve in plaats van een afzonderlijke waarde te verwerken, ze verwerken een reeks waarden. Veel bewerkingen zijn ook versneld via door SIMD geoptimaliseerde implementaties voor .NET 9.
TensorPrimitives biedt nu algemene overbelastingen voor elk type T dat een bepaalde interface implementeert. (De .NET 8-versie bevat alleen overbelastingen voor het bewerken van float reeksen waarden.) De nieuwe CosineSimilarity<T>(ReadOnlySpan<T>, ReadOnlySpan<T>) overbelasting voert bijvoorbeeld cosinus-gelijkenis uit op twee vectoren van float, doubleof Half waarden of waarden van elk ander type dat wordt geïmplementeerd IRootFunctions<TSelf>.
Vergelijk de precisie van de cosinus-gelijkenisbewerking op twee vectoren van het type float versus double:
ReadOnlySpan<float> vector1 = [1, 2, 3];
ReadOnlySpan<float> vector2 = [4, 5, 6];
Console.WriteLine(TensorPrimitives.CosineSimilarity(vector1, vector2));
// Prints 0.9746318
ReadOnlySpan<double> vector3 = [1, 2, 3];
ReadOnlySpan<double> vector4 = [4, 5, 6];
Console.WriteLine(TensorPrimitives.CosineSimilarity(vector3, vector4));
// Prints 0.9746318461970762
Draadbewerking
De threading-API's bevatten verbeteringen voor het doorlopen van taken, voor geprioriteerde kanalen, die in plaats van FIFO hun elementen kunnen ordenen, en Interlocked.CompareExchange voor meer typen.
Task.WhenEach
Er zijn diverse nuttige nieuwe API's toegevoegd voor het werken met Task<TResult> objecten. Met de nieuwe Task.WhenEach methode kunt u taken herhalen terwijl ze zijn voltooid met behulp van een await foreach instructie. U hoeft bijvoorbeeld niet meer herhaaldelijk een reeks taken op te roepen Task.WaitAny om de volgende taak te kiezen die voltooid is.
De volgende code voert meerdere HttpClient aanroepen uit en werkt op hun resultaten zodra ze zijn voltooid.
using HttpClient http = new();
Task<string> dotnet = http.GetStringAsync("http://dot.net");
Task<string> bing = http.GetStringAsync("http://www.bing.com");
Task<string> ms = http.GetStringAsync("http://microsoft.com");
await foreach (Task<string> t in Task.WhenEach(bing, dotnet, ms))
{
Console.WriteLine(t.Result);
}
Niet-gebonden kanaal met prioriteit
Met de System.Threading.Channels naamruimte kunt u fiFO-kanalen (first-in-first-out) maken met behulp van de CreateBounded en CreateUnbounded methoden. Met FIFO-kanalen worden elementen uit het kanaal gelezen in de volgorde waarin ze naar het kanaal zijn geschreven. In .NET 9 is de nieuwe CreateUnboundedPrioritized methode toegevoegd, waardoor de elementen zodanig worden gerangschikt dat het volgende element dat uit het kanaal wordt gelezen, het belangrijkste is, volgens een Comparer<T>.Default of een aangepaste IComparer<T>methode.
In het volgende voorbeeld wordt de nieuwe methode gebruikt om een kanaal te maken dat de getallen 1 tot en met 5 in volgorde uitvoert, ook al worden ze in een andere volgorde naar het kanaal geschreven.
Channel<int> c = Channel.CreateUnboundedPrioritized<int>();
await c.Writer.WriteAsync(1);
await c.Writer.WriteAsync(5);
await c.Writer.WriteAsync(2);
await c.Writer.WriteAsync(4);
await c.Writer.WriteAsync(3);
c.Writer.Complete();
while (await c.Reader.WaitToReadAsync())
{
while (c.Reader.TryRead(out int item))
{
Console.Write($"{item} ");
}
}
// Output: 1 2 3 4 5
Interlocked.CompareExchange voor meer typen
In eerdere versies van .NET hadden Interlocked.Exchange en Interlocked.CompareExchange overbelastingen voor het werken met int, uint, long, ulong, nint, nuint, float, double, en object, alsmede een algemene overbelasting voor het werken met elk verwijzingstype T. In .NET 9 zijn er nieuwe overbelastingen voor atomisch werken met byte, sbyteen shortushort. Ook is de algemene beperking voor de algemene Interlocked.Exchange<T> en Interlocked.CompareExchange<T> overbelasting verwijderd, zodat deze methoden niet langer worden beperkt om alleen met referentietypen te werken. Ze kunnen nu werken met elk primitief type, dat alle bovengenoemde typen boolcharen , evenals elk enum type omvat.