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:

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:

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 RegisterChangeCallbackmetody 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 w StringSegment 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 StringSegmentstruct 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 StringSegmentjako . Kod iteruje segmenty, umożliwiając użytkownikowi interakcję z poszczególnymi segmentelementami .

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:

  1. Za pomocą polecenia StringTokenizer:

    StringBuilder buffer = new();
    
    var tokenizer =
        new StringTokenizer(
            s_nineHundredAutoGeneratedParagraphsOfLoremIpsum,
            new[] { ' ', '.' });
    
    foreach (StringSegment segment in tokenizer)
    {
        buffer.Append(segment.Value);
    }
    
  2. 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 structnull, 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.

Zobacz też