Dela via


Primitiver: Tilläggsbiblioteket för .NET

I den här artikeln får du lära dig mer om biblioteket Microsoft.Extensions.Primitives . Primitiverna i den här artikeln ska inte förväxlas med .NET-primitiva typer från BCL eller C#-språket. I stället fungerar typerna i primitivens bibliotek som byggstenar för några av de kringutrustnings-.NET NuGet-paketen, till exempel:

Ändra meddelanden

Att sprida meddelanden när en ändring sker är ett grundläggande begrepp inom programmering. Det observerade tillståndet för ett objekt kan oftast ändras. När ändringen sker kan implementeringar av Microsoft.Extensions.Primitives.IChangeToken gränssnittet användas för att meddela berörda parter om denna ändring. De tillgängliga implementeringarna är följande:

Som utvecklare kan du också implementera din egen typ. Gränssnittet IChangeToken definierar några egenskaper:

Instansbaserad funktionalitet

Överväg följande exempel på användning av 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

I föregående exempel instansieras en CancellationTokenSource och dess Token skickas till CancellationChangeToken konstruktorn. Det initiala tillståndet HasChanged för skrivs till konsolen. En Action<object?> callback skapas som skriver när återanropet anropas till konsolen. Tokens -metod anropas RegisterChangeCallback(Action<Object>, Object) med tanke på callback. I -instruktionen using avbryts cancellationTokenSource . Detta utlöser återanropet och tillståndet HasChanged för skrivs igen till konsolen.

När du behöver vidta åtgärder från flera ändringskällor använder du CompositeChangeToken. Den här implementeringen aggregerar en eller flera ändringstoken och utlöser varje registrerad återanrop exakt en gång oavsett hur många gånger en ändring utlöses. Ta följande som exempel:

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.

I föregående C#-kod skapas tre CancellationTokenSource objektinstanser och paras ihop med motsvarande CancellationChangeToken instanser. Den sammansatta token instansieras genom att en matris med token skickas till CompositeChangeToken konstruktorn. Action<object?> callback Skapas, men den state här gången används objektet och skrivs till konsolen som ett formaterat meddelande. Återanropet registreras fyra gånger, var och en med ett något annorlunda tillståndsobjektargument. Koden använder en pseudo-slumptalsgenerator för att välja en av källorna för ändringstoken (spelar ingen roll vilken) och anropa dess Cancel() metod. Detta utlöser ändringen och anropar varje registrerat återanrop exakt en gång.

Alternativ static metod

Som ett alternativ till att anropa RegisterChangeCallbackkan du använda den Microsoft.Extensions.Primitives.ChangeToken statiska klassen. Tänk på följande förbrukningsmönster:

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.

Precis som tidigare exempel behöver du en implementering av IChangeToken som skapas av changeTokenProducer. Producenten definieras som en Func<IChangeToken> och det förväntas att detta returnerar en ny token varje anrop. consumer är antingen en Action när du inte använder state, eller en Action<TState> där den generiska typen TState flödar genom ändringsmeddelandet.

Strängtokeniserare, segment och värden

Det är vanligt att interagera med strängar i programutveckling. Olika representationer av strängar parsas, delas eller itereras över. Primitives-biblioteket erbjuder några alternativtyper som hjälper till att göra interaktionen med strängar mer optimerad och effektiv. Tänk på följande typer:

  • StringSegment: En optimerad representation av en delsträng.
  • StringTokenizer: Tokeniserar en string till StringSegment instanser.
  • StringValues: Representerar null, noll, en eller många strängar på ett effektivt sätt.

Typ StringSegment

I det här avsnittet får du lära dig om en optimerad representation av en delsträng som StringSegmentstruct kallas typen . Överväg följande C#-kodexempel som visar några av StringSegment egenskaperna och AsSpan metoden:

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 "

Föregående kod instansierar det StringSegment angivna värdet string , ett offset, och en length. StringSegment.Buffer är det ursprungliga strängargumentet StringSegment.Value och är delsträngen baserat på StringSegment.Offset värdena ochStringSegment.Length.

Structen StringSegment innehåller många metoder för att interagera med segmentet.

Typ StringTokenizer

Objektet StringTokenizer är en structtyp som tokeniserar en string till StringSegment instanser. Tokeniseringen av stora strängar innebär vanligtvis att strängen delas upp och itererar över den. Med det sagt, String.Split kommer förmodligen att tänka på. Dessa API:er är liknande, men i allmänhet StringTokenizer ger bättre prestanda. Tänk först på följande exempel:

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

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

I föregående kod skapas en instans av StringTokenizer typen med 900 automatiskt genererade textstycken Lorem Ipsum och en matris med ett enda värde med ett blankstegstecken ' '. Varje värde i tokenizern representeras som en StringSegment. Koden itererar segmenten så att konsumenten kan interagera med varje segment.

Benchmark-jämförelse StringTokenizer med string.Split

Med de olika sätten att segmentera och diktera strängar känns det lämpligt att jämföra två metoder med ett riktmärke. Med hjälp av BenchmarkDotNet NuGet-paketet bör du överväga följande två benchmark-metoder:

  1. Använda StringTokenizer:

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

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

Båda metoderna ser liknande ut på API-ytan och båda kan dela upp en stor sträng i segment. Benchmark-resultaten nedan visar att StringTokenizer metoden är nästan tre gånger snabbare, men resultaten kan variera. Precis som med alla prestandaöverväganden bör du utvärdera ditt specifika användningsfall.

Metod Medelvärde Fel StdDev Förhållande
Tokenizer 3.315 ms 0,0659 ms 0,0705 ms 0.32
Delad 10,257 ms 0.2018 ms 0,2552 ms 1,00

Förklaring

  • Medelvärde: Aritmetiskt medelvärde för alla mått
  • Fel: Hälften av konfidensintervallet på 99,9 %
  • Standardavvikelse: Standardavvikelse för alla mätningar
  • Median: Värde som skiljer den högre hälften av alla mått (50:e percentilen)
  • Förhållande: Medelvärde för förhållandefördelningen (aktuell/baslinje)
  • Standardavvikelse för förhållande: Standardavvikelse för förhållandefördelningen (aktuell/baslinje)
  • 1 ms: 1 millisekunder (0,001 sek)

Mer information om benchmarking med .NET finns i BenchmarkDotNet.

Typ StringValues

Objektet StringValues är en struct typ som representerar null, noll, en eller flera strängar på ett effektivt sätt. Typen StringValues kan konstrueras med någon av följande syntaxer: string? eller string?[]?. Tänk på följande C#-kod med hjälp av texten från föregående exempel:

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

Föregående kod instansierar ett StringValues objekt med en matris med strängvärden. Skrivs StringValues.Count till konsolen.

Typen StringValues är en implementering av följande samlingstyper:

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

Därför kan den itereras över och var och en value kan interageras med efter behov.

Se även