Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W tym artykule znajdziesz informacje o bibliotece Microsoft.Extensions.Primitives . Typy prymitywne w tym artykule nie powinny być mylone z prymitywnymi typami .NET z Biblioteki Klasy Bazowej (BCL) ani z typami 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.ConfigurationMicrosoft.Extensions.Configuration.FileExtensionsMicrosoft.Extensions.FileProviders.CompositeMicrosoft.Extensions.FileProviders.PhysicalMicrosoft.Extensions.Logging.EventSourceMicrosoft.Extensions.OptionsSystem.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 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 tworzony jest obiekt CancellationTokenSource, a jego Token jest przekazywane do konstruktora CancellationChangeToken. Początkowy stan HasChanged jest zapisywany w konsoli programu . Tworzony jest Action<object?> callback, który zapisuje do konsoli, gdy wywoływane jest wywołanie zwrotne. Metoda tokenu RegisterChangeCallback(Action<Object>, Object) jest wywoływana, biorąc pod uwagę callback. W ramach instrukcji using element cancellationTokenSource jest anulowany. Spowoduje to wyzwolenie wywołania zwrotnego, a stan HasChanged zostanie ponownie zapisany w konsoli.
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 C# tworzone są trzy instancje obiektów CancellationTokenSource i są one parowane z odpowiednimi instancjami CancellationChangeToken. Token złożony jest tworzony poprzez przekazanie tablicy tokenów do konstruktora CompositeChangeToken. Obiekt Action<object?> callback jest tworzony, ale tym razem obiekt state jest używany i wypisywany 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 podejście static
Alternatywą dla wywołania RegisterChangeCallback 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 Func<IChangeToken> i oczekuje się, że przy każdym wywołaniu zwróci nowy token. Ten consumer jest albo Action w przypadku nieużywania state, albo Action<TState>, gdzie typ ogólny TState 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 typów prymitywnych oferuje kilka starannie dobranych typów, które optymalizują i zwiększają efektywność interakcji z ciągami. Rozważ następujące typy:
- StringSegment: zoptymalizowana reprezentacja podłańcucha.
-
StringTokenizer: tokenizuje element
stringwStringSegmentwystąpieniach. -
StringValues: reprezentuje
null, zero, jeden lub wiele ciągów w wydajny sposób.
Typ StringSegment
W tej sekcji dowiesz się o zoptymalizowanej reprezentacji ciągu znaków znanej jako typ StringSegmentstruct. 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 instancję StringSegment na podstawie wartości string, offset oraz length. Jest StringSegment.Buffer to oryginalny argument ciągu, a StringSegment.Value to podciąg oparty 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 na wystąpienia StringSegment. Tokenizacja dużych ciągów zwykle polega na podzieleniu ciągu i iterowaniu na nim. Powiedziawszy to, 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 tworzony jest obiekt typu StringTokenizer, dla 900 wygenerowanych automatycznie akapitów tekstu Lorem Ipsum i tablicy z pojedynczą wartością znaku ' ' odstępu. Każda wartość w tokenizatorze jest reprezentowana jako StringSegment. Kod iteruje segmenty, umożliwiając użytkownikowi interakcję z poszczególnymi segmentelementami .
Porównanie porównawcze StringTokenizer z string.Split
Dzięki różnorodnym sposobom dzielenia i przetwarzania ciągów, porównanie dwóch metod przy użyciu testu porównawczego wydaje się właściwe. 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.
| Metoda | Średnia | Błąd | StdDev | Współczynnik |
|---|---|---|---|---|
| narzędzie do tokenizacji | 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, który w wydajny sposób reprezentuje struct, null, zero, jeden lub wiele ciągów znaków. 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 instancję obiektu StringValues, korzystając z tablicy wartości ciągów. Element StringValues.Count jest zapisywany na konsolę.
Typ StringValues jest implementacją następujących typów kolekcji:
IList<string>ICollection<string>IEnumerable<string>IEnumerableIReadOnlyList<string>IReadOnlyCollection<string>
W związku z tym można go iterować, a każdy value może być używany w zależności od potrzeb.