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:
Microsoft.Extensions.Configuration
Microsoft.Extensions.Configuration.FileExtensions
Microsoft.Extensions.FileProviders.Composite
Microsoft.Extensions.FileProviders.Physical
Microsoft.Extensions.Logging.EventSource
Microsoft.Extensions.Options
System.Text.Json
Ä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:
- IChangeToken.HasChanged: Hämtar ett värde som anger om en ändring har inträffat.
- IChangeToken.ActiveChangeCallbacks: Anger om token proaktivt genererar återanrop. Om
false
måste tokenkonsumenten avsökaHasChanged
för att identifiera ändringar.
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 RegisterChangeCallback
kan 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
tillStringSegment
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 StringSegment struct
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:
Använda StringTokenizer:
StringBuilder buffer = new(); var tokenizer = new StringTokenizer( s_nineHundredAutoGeneratedParagraphsOfLoremIpsum, new[] { ' ', '.' }); foreach (StringSegment segment in tokenizer) { buffer.Append(segment.Value); }
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.