Delen via


Primitieven: de uitbreidingsbibliotheek voor .NET

In dit artikel vindt u informatie over de bibliotheek Microsoft.Extensions.Primitives . De primitieven in dit artikel zijn niet te verwarren met .NET primitieve typen uit de BCL of die van de C#-taal. In plaats daarvan fungeren de typen binnen de primitieve bibliotheek als bouwstenen voor sommige randapparatuur .NET NuGet-pakketten, zoals:

Meldingen wijzigen

Meldingen doorgeven wanneer een wijziging plaatsvindt, is een fundamenteel concept in programmeren. De waargenomen toestand van een object kan vaker dan niet worden gewijzigd. Wanneer er veranderingen optreden, kunnen implementaties van de interface worden gebruikt om belanghebbenden op de Microsoft.Extensions.Primitives.IChangeToken hoogte te stellen van deze wijziging. De beschikbare implementaties zijn als volgt:

Als ontwikkelaar kunt u ook uw eigen type implementeren. De IChangeToken interface definieert enkele eigenschappen:

Functionaliteit op basis van exemplaren

Bekijk het volgende voorbeeldgebruik van:CancellationChangeToken

CancellationTokenSource cancellationTokenSource = new();
CancellationChangeToken cancellationChangeToken = new(cancellationTokenSource.Token);

Console.WriteLine($"HasChanged: {cancellationChangeToken.HasChanged}");

static void callback(object? _) =>
    Console.WriteLine("The callback was invoked.");

using (IDisposable subscription =
    cancellationChangeToken.RegisterChangeCallback(callback, null))
{
    cancellationTokenSource.Cancel();
}

Console.WriteLine($"HasChanged: {cancellationChangeToken.HasChanged}\n");

// Outputs:
//     HasChanged: False
//     The callback was invoked.
//     HasChanged: True

In het voorgaande voorbeeld wordt een CancellationTokenSource instantie geïnstantieerd en Token wordt het doorgegeven aan de CancellationChangeToken constructor. De initiële status wordt HasChanged naar de console geschreven. Er Action<object?> callback wordt een gemaakt die schrijft wanneer de callback wordt aangeroepen naar de console. De methode van RegisterChangeCallback(Action<Object>, Object) het token wordt aangeroepen, gegeven de callback. Binnen de using instructie wordt de cancellationTokenSource bewerking geannuleerd. Hiermee wordt de callback geactiveerd en wordt de status HasChanged opnieuw naar de console geschreven.

Wanneer u actie moet ondernemen vanuit meerdere wijzigingsbronnen, gebruikt u de CompositeChangeToken. Met deze implementatie worden een of meer wijzigingstokens samengevoegd en wordt elke geregistreerde callback precies één keer geactiveerd, ongeacht het aantal keren dat een wijziging wordt geactiveerd. Kijk een naar het volgende voorbeeld:

CancellationTokenSource firstCancellationTokenSource = new();
CancellationChangeToken firstCancellationChangeToken = new(firstCancellationTokenSource.Token);

CancellationTokenSource secondCancellationTokenSource = new();
CancellationChangeToken secondCancellationChangeToken = new(secondCancellationTokenSource.Token);

CancellationTokenSource thirdCancellationTokenSource = new();
CancellationChangeToken thirdCancellationChangeToken = new(thirdCancellationTokenSource.Token);

var compositeChangeToken =
    new CompositeChangeToken(
        new IChangeToken[]
        {
            firstCancellationChangeToken,
            secondCancellationChangeToken,
            thirdCancellationChangeToken
        });

static void callback(object? state) =>
    Console.WriteLine($"The {state} callback was invoked.");

// 1st, 2nd, 3rd, and 4th.
compositeChangeToken.RegisterChangeCallback(callback, "1st");
compositeChangeToken.RegisterChangeCallback(callback, "2nd");
compositeChangeToken.RegisterChangeCallback(callback, "3rd");
compositeChangeToken.RegisterChangeCallback(callback, "4th");

// It doesn't matter which cancellation source triggers the change.
// If more than one trigger the change, each callback is only fired once.
Random random = new();
int index = random.Next(3);
CancellationTokenSource[] sources = new[]
{
    firstCancellationTokenSource,
    secondCancellationTokenSource,
    thirdCancellationTokenSource
};
sources[index].Cancel();

Console.WriteLine();

// Outputs:
//     The 4th callback was invoked.
//     The 3rd callback was invoked.
//     The 2nd callback was invoked.
//     The 1st callback was invoked.

In de voorgaande C#-code worden drie CancellationTokenSource objectexemplaren gemaakt en gekoppeld aan bijbehorende CancellationChangeToken exemplaren. Het samengestelde token wordt geïnstantieerd door een matrix van de tokens door te geven aan de CompositeChangeToken constructor. Het Action<object?> callback object wordt gemaakt, maar deze keer wordt het object gebruikt en naar de state console geschreven als een opgemaakt bericht. De callback wordt vier keer geregistreerd, elk met een iets ander statusobjectargument. De code maakt gebruik van een pseudo-willekeurige getalgenerator om een van de wijzigingstokenbronnen te kiezen (maakt niet uit welke) en roept de methode aan Cancel() . Hierdoor wordt de wijziging geactiveerd, waarbij elke geregistreerde callback precies één keer wordt aangeroepen.

Alternatieve static benadering

Als alternatief voor aanroepen RegisterChangeCallbackkunt u de Microsoft.Extensions.Primitives.ChangeToken statische klasse gebruiken. Houd rekening met het volgende verbruikspatroon:

CancellationTokenSource cancellationTokenSource = new();
CancellationChangeToken cancellationChangeToken = new(cancellationTokenSource.Token);

IChangeToken producer()
{
    // The producer factory should always return a new change token.
    // If the token's already fired, get a new token.
    if (cancellationTokenSource.IsCancellationRequested)
    {
        cancellationTokenSource = new();
        cancellationChangeToken = new(cancellationTokenSource.Token);
    }

    return cancellationChangeToken;
}

void consumer() => Console.WriteLine("The callback was invoked.");

using (ChangeToken.OnChange(producer, consumer))
{
    cancellationTokenSource.Cancel();
}

// Outputs:
//     The callback was invoked.

Net als in eerdere voorbeelden hebt u een implementatie nodig die IChangeToken wordt geproduceerd door de changeTokenProducer. De producent wordt gedefinieerd als een en Func<IChangeToken> verwacht dat hiermee elke aanroep een nieuw token wordt geretourneerd. Dit consumer is een Action wanneer het niet wordt gebruikt stateof een Action<TState> locatie waar het algemene type TState door de wijzigingsmelding loopt.

Tekenreekstokenizers, segmenten en waarden

Interactie met tekenreeksen is gebruikelijk bij het ontwikkelen van toepassingen. Verschillende weergaven van tekenreeksen worden geparseerd, gesplitst of opnieuw bekeken. De primitieve bibliotheek biedt een aantal keuzetypen die helpen om interactie met tekenreeksen beter en efficiënter te maken. Houd rekening met de volgende typen:

  • StringSegment: Een geoptimaliseerde weergave van een subtekenreeks.
  • StringTokenizer: Hiermee wordt een tokenize string in StringSegment exemplaren.
  • StringValues: Vertegenwoordigt null, nul, één of veel tekenreeksen op een efficiënte manier.

Het StringSegment type

In deze sectie leert u meer over een geoptimaliseerde weergave van een subtekenreeks die het type wordt genoemd StringSegment struct . Bekijk het volgende C#-codevoorbeeld met enkele eigenschappen StringSegment en de AsSpan methode:

var segment =
    new StringSegment(
        "This a string, within a single segment representation.",
        14, 25);

Console.WriteLine($"Buffer: \"{segment.Buffer}\"");
Console.WriteLine($"Offset: {segment.Offset}");
Console.WriteLine($"Length: {segment.Length}");
Console.WriteLine($"Value: \"{segment.Value}\"");

Console.Write("Span: \"");
foreach (char @char in segment.AsSpan())
{
    Console.Write(@char);
}
Console.Write("\"\n");

// Outputs:
//     Buffer: "This a string, within a single segment representation."
//     Offset: 14
//     Length: 25
//     Value: " within a single segment "
//     " within a single segment "

Met de voorgaande code wordt de StringSegment opgegeven string waarde, een offset, en een length. Dit StringSegment.Buffer is het oorspronkelijke tekenreeksargument en de StringSegment.Value subtekenreeks is gebaseerd op de StringSegment.Offset en StringSegment.Length waarden.

De StringSegment struct biedt veel methoden voor interactie met het segment.

Het StringTokenizer type

Het StringTokenizer object is een structtype dat een string tokenizet in StringSegment exemplaren. De tokenisatie van grote tekenreeksen omvat meestal het splitsen van de tekenreeks en het herhalen ervan. Dat gezegd hebbende, String.Split komt waarschijnlijk in gedachten. Deze API's zijn vergelijkbaar, maar in het algemeen StringTokenizer bieden ze betere prestaties. Bekijk eerst het volgende voorbeeld:

var tokenizer =
    new StringTokenizer(
        s_nineHundredAutoGeneratedParagraphsOfLoremIpsum,
        new[] { ' ' });

foreach (StringSegment segment in tokenizer)
{
    // Interact with segment
}

In de voorgaande code wordt een exemplaar van het StringTokenizer type gemaakt met 900 automatisch gegenereerde alinea's met Lorem Ipsum tekst en een matrix met één waarde van een witruimteteken ' '. Elke waarde binnen de tokenizer wordt weergegeven als een StringSegment. De code doorloopt de segmenten, zodat de consument met elk segmentsegment kan communiceren.

Benchmark vergeleken met StringTokenizerstring.Split

Met de verschillende manieren om tekenreeksen te segmenteren en te koppelen, is het geschikt om twee methoden te vergelijken met een benchmark. Houd rekening met de volgende twee benchmarkmethoden met behulp van het NuGet-pakket BenchmarkDotNet :

  1. Met behulp van StringTokenizer:

    StringBuilder buffer = new();
    
    var tokenizer =
        new StringTokenizer(
            s_nineHundredAutoGeneratedParagraphsOfLoremIpsum,
            new[] { ' ', '.' });
    
    foreach (StringSegment segment in tokenizer)
    {
        buffer.Append(segment.Value);
    }
    
  2. Met behulp van String.Split:

    StringBuilder buffer = new();
    
    string[] tokenizer =
        s_nineHundredAutoGeneratedParagraphsOfLoremIpsum.Split(
            new[] { ' ', '.' });
    
    foreach (string segment in tokenizer)
    {
        buffer.Append(segment);
    }
    

Beide methoden zien er ongeveer als volgt uit op het oppervlak van de API en ze kunnen beide een grote tekenreeks splitsen in segmenten. In de onderstaande benchmarkresultaten ziet u dat de StringTokenizer aanpak bijna drie keer sneller is, maar de resultaten kunnen variëren. Net als bij alle prestatieoverwegingen moet u uw specifieke use case evalueren.

Wijze Gemiddelde Error StdDev Verhouding
Tokenizer 3.315 ms 0.0659 ms 0.0705 ms 0.32
Split 10.257 ms 0.2018 ms 0,2552 ms 1,00

Legenda

  • Gemiddelde: Rekenkundig gemiddelde van alle metingen
  • Fout: de helft van het betrouwbaarheidsinterval van 99,9%
  • Standaarddeviatie: Standaarddeviatie van alle metingen
  • Mediaan: Waarde die de hogere helft van alle metingen scheidt (50e percentiel)
  • Verhouding: gemiddelde van de verhoudingsverdeling (huidige/basislijn)
  • Standaarddeviatie verhouding: Standaarddeviatie van de verhoudingsverdeling (huidige/basislijn)
  • 1 ms: 1 milliseconden (0,001 sec)

Zie BenchmarkDotNet voor meer informatie over benchmarking met .NET.

Het StringValues type

Het StringValues object is een struct type dat op een efficiënte manier nul, één of veel tekenreeksen vertegenwoordigt null. Het StringValues type kan worden samengesteld met een van de volgende syntaxis: string? of string?[]?. Houd rekening met de volgende C#-code met behulp van de tekst uit het vorige voorbeeld:

StringValues values =
    new(s_nineHundredAutoGeneratedParagraphsOfLoremIpsum.Split(
        new[] { '\n' }));

Console.WriteLine($"Count = {values.Count:#,#}");

foreach (string? value in values)
{
    // Interact with the value
}
// Outputs:
//     Count = 1,799

Met de voorgaande code wordt een StringValues object geïnstitueert op basis van een matrix met tekenreekswaarden. De StringValues.Count wordt naar de console geschreven.

Het StringValues type is een implementatie van de volgende verzamelingstypen:

  • IList<string>
  • ICollection<string>
  • IEnumerable<string>
  • IEnumerable
  • IReadOnlyList<string>
  • IReadOnlyCollection<string>

Als zodanig kan het worden ge curseerd en kan elk value naar behoefte worden gebruikt.

Zie ook