Typy pierwotne: biblioteka rozszerzeń dla platformy .NET
W tym artykule znajdziesz informacje o bibliotece Microsoft.Extensions.Primitives . Typy pierwotne w tym artykule nie należy mylić z typami pierwotnymi platformy .NET z listy BCL lub języka C#. Zamiast tego typy w bibliotece pierwotnej służą jako bloki konstrukcyjne dla niektórych peryferyjnych pakietów NuGet platformy .NET, takich jak:
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
Zmienianie powiadomień
Propagowanie powiadomień w przypadku zmiany jest podstawową koncepcją programowania. Obserwowany stan obiektu częściej niż nie może ulec zmianie. W przypadku zmiany implementacje interfejsu Microsoft.Extensions.Primitives.IChangeToken mogą służyć do powiadamiania zainteresowanych stron o wspomnianej zmianie. Dostępne implementacje są następujące:
Jako deweloper możesz również zaimplementować własny typ. Interfejs IChangeToken definiuje kilka właściwości:
- IChangeToken.HasChanged: Pobiera wartość wskazującą, czy nastąpiła zmiana.
- IChangeToken.ActiveChangeCallbacks: wskazuje, czy token będzie aktywnie zgłaszać wywołania zwrotne. Jeśli
false
użytkownik tokenu musi sondowaćHasChanged
, aby wykryć zmiany.
Funkcje oparte na wystąpieniach
Rozważmy następujące przykładowe użycie elementu 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
W poprzednim przykładzie wystąpienie obiektu a CancellationTokenSource jest tworzone, a jego Token wystąpienie jest przekazywane do konstruktora CancellationChangeToken . Początkowy stan HasChanged
jest zapisywany w konsoli programu . Tworzony Action<object?> callback
jest element, który zapisuje podczas wywoływania wywołania zwrotnego do konsoli programu . Metoda tokenu jest wywoływana RegisterChangeCallback(Action<Object>, Object) , biorąc pod uwagę metodę callback
. W ramach instrukcji using
element cancellationTokenSource
jest anulowany. Spowoduje to wyzwolenie wywołania zwrotnego, a stan HasChanged
polecenia zostanie ponownie zapisany w konsoli programu .
Jeśli musisz podjąć akcję z wielu źródeł zmian, użyj polecenia CompositeChangeToken. Ta implementacja agreguje co najmniej jeden token zmiany i uruchamia każde zarejestrowane wywołanie zwrotne dokładnie raz, niezależnie od liczby wyzwalanych zmian. Rozważmy następujący przykład:
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.
W poprzednim kodzie języka C# tworzone są trzy CancellationTokenSource wystąpienia obiektów i są sparowane z odpowiednimi CancellationChangeToken wystąpieniami. Token złożony jest tworzone przez przekazanie tablicy tokenów do konstruktora CompositeChangeToken . Obiekt Action<object?> callback
jest tworzony, ale tym razem state
obiekt jest używany i zapisywany w konsoli jako sformatowany komunikat. Wywołanie zwrotne jest rejestrowane cztery razy, z których każdy ma nieco inny argument obiektu stanu. Kod używa generatora liczb pseudolosowych, aby wybrać jedno ze źródeł tokenu zmiany (nie ma znaczenia, który z nich) i wywołać jego Cancel() metodę. Spowoduje to wyzwolenie zmiany, wywołując każde zarejestrowane wywołanie zwrotne dokładnie raz.
Alternatywne static
podejście
Alternatywą dla wywołania RegisterChangeCallback
metody jest użycie klasy statycznej Microsoft.Extensions.Primitives.ChangeToken . Rozważmy następujący wzorzec zużycia:
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.
Podobnie jak w poprzednich przykładach, potrzebna będzie implementacja IChangeToken
, która jest utworzona przez element changeTokenProducer
. Producent jest zdefiniowany jako element Func<IChangeToken>
i oczekuje się, że zwróci nowy token przy każdym wywołaniu. Jest consumer
to element Action
, jeśli nie jest używany state
, lub gdzie Action<TState>
typ TState
ogólny przepływa przez powiadomienie o zmianie.
Tokenizatory ciągów, segmenty i wartości
Interakcja z ciągami jest powszechna w tworzeniu aplikacji. Różne reprezentacje ciągów są analizowane, podzielone lub iterowane. Biblioteka pierwotnych oferuje kilka typów wyboru, które ułatwiają interakcję z ciągami bardziej zoptymalizowanymi i wydajnymi. Rozważ następujące typy:
- StringSegment: zoptymalizowana reprezentacja podciągów.
- StringTokenizer: tokenizuje element
string
wStringSegment
wystąpieniach. - StringValues: reprezentuje
null
, zero, jeden lub wiele ciągów w wydajny sposób.
Typ StringSegment
W tej sekcji dowiesz się więcej o zoptymalizowanej reprezentacji podciągów znanej StringSegment struct
jako typ. Rozważmy następujący przykład kodu w języku C# przedstawiający niektóre StringSegment
właściwości i metodę AsSpan
:
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 "
Powyższy kod tworzy wystąpienie StringSegment
danej string
wartości, wartości offset
, i .length
Jest StringSegment.Buffer to oryginalny argument ciągu, a StringSegment.Value element jest podciągem opartym na wartościach StringSegment.Offset i StringSegment.Length .
Struktura StringSegment
udostępnia wiele metod interakcji z segmentem.
Typ StringTokenizer
Obiekt StringTokenizer jest typem struktury, który tokenizuje string
obiekt w StringSegment
wystąpieniach. Tokenizacja dużych ciągów zwykle polega na podzieleniu ciągu i iterowaniu na nim. Z tym powiedzenie, String.Split prawdopodobnie przychodzi na myśl. Te interfejsy API są podobne, ale ogólnie StringTokenizer zapewnia lepszą wydajność. Najpierw rozważmy następujący przykład:
var tokenizer =
new StringTokenizer(
s_nineHundredAutoGeneratedParagraphsOfLoremIpsum,
new[] { ' ' });
foreach (StringSegment segment in tokenizer)
{
// Interact with segment
}
W poprzednim kodzie tworzone jest wystąpienie StringTokenizer
typu, na podstawie 900 automatycznie generowanych akapitów Lorem Ipsum tekstu i tablicy z pojedynczą wartością znaku ' '
odstępu. Każda wartość w tokenizatorze jest reprezentowana StringSegment
jako . Kod iteruje segmenty, umożliwiając użytkownikowi interakcję z poszczególnymi segment
elementami .
Porównanie porównawcze StringTokenizer
z string.Split
W przypadku różnych sposobów fragmentowania i fragmentowania ciągów wydaje się być odpowiednie do porównywania dwóch metod z testem porównawczym. Korzystając z pakietu NuGet BenchmarkDotNet, rozważ następujące dwie metody testu porównawczego:
Za pomocą polecenia StringTokenizer:
StringBuilder buffer = new(); var tokenizer = new StringTokenizer( s_nineHundredAutoGeneratedParagraphsOfLoremIpsum, new[] { ' ', '.' }); foreach (StringSegment segment in tokenizer) { buffer.Append(segment.Value); }
Za pomocą polecenia String.Split:
StringBuilder buffer = new(); string[] tokenizer = s_nineHundredAutoGeneratedParagraphsOfLoremIpsum.Split( new[] { ' ', '.' }); foreach (string segment in tokenizer) { buffer.Append(segment); }
Obie metody wyglądają podobnie w obszarze powierzchni interfejsu API i są w stanie podzielić duży ciąg na fragmenty. Poniższe wyniki testu porównawczego pokazują, że StringTokenizer
podejście jest prawie trzy razy szybsze, ale wyniki mogą się różnić. Podobnie jak w przypadku wszystkich zagadnień dotyczących wydajności, należy ocenić konkretny przypadek użycia.
Method | Średnia | Błąd | StdDev | Współczynnik |
---|---|---|---|---|
Tokenizer | 3.315 ms | 0.0659 ms | 0.0705 ms | 0.32 |
Podział | 10.257 ms | 0.2018 ms | 0,2552 ms | 1.00 |
Legenda
- Średnia: średnia arytmetyczna wszystkich pomiarów
- Błąd: Połowa z 99,9% przedziału ufności
- Odchylenie standardowe: odchylenie standardowe wszystkich pomiarów
- Mediana: Wartość oddzielająca wyższą połowę wszystkich pomiarów (50. percentyl)
- Współczynnik: średnia rozkładu współczynników (bieżąca/bazowa)
- Odchylenie standardowe współczynnika: odchylenie standardowe rozkładu współczynnika (bieżący/punkt odniesienia)
- 1 ms: 1 milisekund (0,001 s)
Aby uzyskać więcej informacji na temat testów porównawczych za pomocą platformy .NET, zobacz BenchmarkDotNet.
Typ StringValues
Obiekt StringValues jest typem reprezentującym struct
null
, zero, jeden lub wiele ciągów w wydajny sposób. Typ StringValues
można skonstruować przy użyciu jednej z następujących składni: string?
lub string?[]?
. Korzystając z tekstu z poprzedniego przykładu, rozważ następujący kod języka C#:
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
Powyższy kod tworzy wystąpienie StringValues
obiektu, biorąc pod uwagę tablicę wartości ciągu. Element StringValues.Count jest zapisywany w konsoli programu .
Typ StringValues
jest implementacją następujących typów kolekcji:
IList<string>
ICollection<string>
IEnumerable<string>
IEnumerable
IReadOnlyList<string>
IReadOnlyCollection<string>
W związku z tym można go iterować, a każda z nich value
może być w miarę potrzeb wchodzić w interakcje.