Primitívek: A .NET bővítménytára

Ebben a cikkben megismerheti a Microsoft.Extensions.Primitives kódtárat . A cikkben szereplő primitívek nem tévesztendők össze a BCL-ből származó .NET primitív típusokkal vagy a C# nyelvével. Ehelyett a primitív kódtárban lévő típusok a periféria .NET NuGet-csomagjainak építőelemei, például:

Értesítések módosítása

Az értesítések propagálása változás esetén a programozás alapvető fogalma. Az objektumok megfigyelt állapota gyakran változik. Ha változás történik, a felület implementációi felhasználhatók az Microsoft.Extensions.Primitives.IChangeToken érdekelt felek értesítésére az említett változásról. Az elérhető implementációk a következők:

Fejlesztőként ön is szabadon implementálhatja saját típusát. A IChangeToken felület néhány tulajdonságot határoz meg:

  • IChangeToken.HasChanged: Olyan értéket kap, amely jelzi, hogy történt-e változás.
  • IChangeToken.ActiveChangeCallbacks: Azt jelzi, hogy a token proaktívan kezdeményez-e visszahívásokat. Ha false, a jogkivonat-fogyasztónak figyelemmel kell kísérnie HasChanged a változások észleléséhez.

Példányalapú funkciók

Vegye figyelembe a következő példa használatát: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

Az előző példában egy CancellationTokenSource példányosodik, és annak Token példányát adják át a CancellationChangeToken konstruktornak. A rendszer a kezdeti állapotot HasChanged a konzolra írja. Egy Action<object?> callback jön létre, amely akkor ír a konzolra, amikor a visszahívás meghívásra kerül. A jogkivonat RegisterChangeCallback(Action<Object>, Object) metódusát meghívják, a callback alapján. A using utasításon belül a cancellationTokenSource törlésre kerül. Ez aktiválja a visszahívást, és az állapot HasChanged ismét meg lesz írva a konzolra.

Ha több változási forrásból kell műveletet elvégeznie, használja a CompositeChangeToken. Ez a megvalósítás egy vagy több változásértesítést összesít, és minden regisztrált visszahívást pontosan egy alkalommal hajt végre, függetlenül attól, hogy a módosítás hányszor aktiválódik. Vegyük a következő példát:

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.

Az előző C#-kódban három CancellationTokenSource objektumpéldány jön létre és párosítva a megfelelő CancellationChangeToken példányokkal. Az összetett jogkivonat példányosítása a jogkivonatok tömbjének a CompositeChangeToken konstruktorhoz való átadásával történik. A Action<object?> callback létrejön, de ezúttal a state objektumot használják, és formázott üzenetként írják a konzolra. A visszahívás négy alkalommal van regisztrálva, mindegyikhez egy kissé eltérő állapotobjektum-argumentum tartozik. A kód egy pszeudo-véletlenszerű számgenerátor használatával választja ki a változási jogkivonat forrásainak egyikét (nem számít, melyiket), és meghívja annak metódusát Cancel() . Ez aktiválja a módosítást, és minden regisztrált visszahívást pontosan egyszer hív meg.

Vegye figyelembe, hogy a(z) CompositeChangeTokenActiveChangeCallbacks eleme true, ha legalább egy belső token támogatja az aktív visszahívásokat. Az összetett token csak azokra az aktív belső tokenekre iratkozik fel. A passzív belső token változásának észleléséhez kérdezze le az összetett token HasChanged tulajdonságát.

Alternatív static megközelítés

A hívás RegisterChangeCallbackalternatívaként használhatja a statikus osztályt Microsoft.Extensions.Primitives.ChangeToken . Vegye figyelembe a következő fogyasztási mintát:

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.

A korábbi példákhoz hasonlóan szüksége lesz egy, a(z) changeTokenProducer által visszaadott IChangeToken implementációra. A gyártó egy Func<IChangeToken?>. A változtatás után vissza kell adnia egy új tokent a következő regisztrációhoz. Ha a függvény null értékkel tér vissza, a ChangeToken.OnChange nem regisztrál callbacket. A fogyasztó regisztrációjának megszüntetéséhez szabadítsa fel a OnChange által visszaadott IDisposable elemet.

Szinkron felhasználó esetén adja meg az alábbiak egyikét: Action vagy Action<TState>. A gyártótól vagy a fogyasztótól származó kivételek továbbterjednek ahhoz a hívóhoz, amely regisztrálja vagy aktiválja a tokent.

A .NET 11-től kezdődően egy aszinkron feldolgozóhoz átadhat egy Func<Task> vagy Func<TState, Task> elemet. ChangeToken.OnChange megvárja, amíg a visszaadott feladat befejeződik, mielőtt a fogyasztó újra meghívható lenne. Ez a viselkedés megakadályozza az egyidejű fogyasztói hívásokat, de több módosítást is kombinálhat, amelyek akkor fordulnak elő, amikor a feladat egy későbbi visszahívásba fut. A felhasználó kódja által a feladatának visszaadása előtt kiváltott kivétel továbbterjed a tokent regisztráló vagy aktiváló kódra. A feladat visszaadása után előforduló kivételeket a rendszer nem figyeli ChangeToken.OnChangemeg.

Note

A 11-.NET meglévő kódjának újrafordításakor a async lambda a szinkron Action túlterhelés helyett egy új feladatvisszaadó túlterheléshez kapcsolódhat. Az új kötés a visszahívás időzítését a viselkedésről async void a feladatalapú viselkedésre módosítja. További információkért lásd: ChangeToken.OnChange async overloads rebind existing Task-returning callbacks.

Sztringtokenizálók, szegmensek és értékek

A sztringekkel való interakció gyakori az alkalmazásfejlesztésben. A karakterláncok különféle ábrázolásait elemezhetjük, feloszthatjuk vagy iterálhatunk rajtuk. A primitívek kódtára kínál néhány választható típust, amelyek segítenek optimalizáltabbá és hatékonyabbá tenni a sztringekkel való interakciót. Vegye figyelembe a következő típusokat:

  • StringSegment: Egy karakterlánc részleteinek optimalizált ábrázolása.
  • StringTokenizer: Tokenekre bont stringStringSegment példányt.
  • StringValues: Hatékonyan jelöli null, a nulla, egy vagy több karakterláncot.

A StringSegment típus

Ebben a szakaszban megismerheti a típusként StringSegmentstruct ismert alsztringek optimalizált ábrázolását. Tekintse meg a következő C#-kód példáját, amely néhány StringSegment tulajdonságot és metódust AsSpan mutat be:

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 "

Az előző kód példányosítja a StringSegment egy string értéket, egy offset és egy length alapján. Az StringSegment.Buffer az eredeti sztringargumentum, és a StringSegment.Value részsztring a StringSegment.Offset és StringSegment.Length értékek alapján jön létre.

A StringSegment szerkezet számos módszert kínál a szegmensekkel való interakcióhoz.

A StringTokenizer típus

Az StringTokenizer objektum egy struktúratípus, amely a string-et StringSegment példányokra bontja. A nagy karakterláncok tokenizálása általában magában foglalja a karakterlánc szétválasztását és iterálását. Ha ez így van, String.Split valószínűleg eszembe jut. Ezek az API-k hasonlóak, de általában StringTokenizer jobb teljesítményt nyújtanak. Először vegye figyelembe a következő példát:

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

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

A korábbi kódban létrehozunk egy StringTokenizer típusú példányt, 900 automatikusan generált Lorem Ipsum szöveg bekezdés és egy szóköz karaktert tartalmazó ' ' tömb alapján. A tokenizer minden egyes értékét egy StringSegment jelöli. A kód iterálja a szegmenseket, lehetővé téve a felhasználó számára az egyes segment.

Összehasonlítás a StringTokenizer teljesítménymutatóval string.Split

A sztringek szeletelésének és feldarabolásának különböző módjaival célszerű két módszert összehasonlítani egy referenciaértékkel. A BenchmarkDotNet NuGet-csomag használatával fontolja meg a következő két benchmark metódust:

  1. Használva: StringTokenizer

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

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

Mindkét metódus hasonlóan néz ki az API felületén, és mindkettő képes nagy sztringeket darabkákra osztani. Az alábbi teljesítményteszt eredményei azt mutatják, hogy a StringTokenizer megközelítés közel háromszor gyorsabb, de az eredmények eltérőek lehetnek. Mint minden teljesítménybeli szempontnál, a konkrét használati esetet is ki kell értékelnie.

Metódus Középérték Hiba StdDev Arány
Tokenizáló 3,315 ms 0,0659 ms 0,0705 ms 0.32
Szakít 10,257 ms 0,2018 ms 0,2552 ms 1,00

Legenda

  • Középérték: Az összes mérés számtani középértéke
  • Hiba: A 99,9%-os megbízhatósági intervallum fele
  • Szórás: Az összes mérés standard szórása
  • Medián: Az összes mérés felső felét elválasztó érték (50. percentilis)
  • Arány: Az arányeloszlás középértéke (aktuális/alapkonfiguráció)
  • Arány szórása: Az arányeloszlás szórása (aktuális/alapállapot)
  • 1 ms: 1 ezredmásodperc (0,001 mp)

A .NET-hez való teljesítménytesztről további információt a BenchmarkDotNetben talál.

A StringValues típus

Az StringValues objektum olyan struct típus, amely hatékonyan jelöl null, nulla, egy vagy több sztringet. A StringValues típus a következő szintaxisok egyikével hozható létre: string? vagy string?[]?. Az előző példa szövegét használva vegye figyelembe a következő C#-kódot:

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

Az előző kód egy sztringértékek tömbje alapján példányosít egy StringValues objektumot. A StringValues.Count rendszer a konzolra írja.

A StringValues típus a következő gyűjteménytípusok implementációja:

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

Ennek megfelelően át lehet iterálni, és mindegyik value szükség szerint használható.

Lásd még