Bagikan melalui


Primitif: Pustaka ekstensi untuk .NET

Dalam artikel ini, Anda akan mempelajari tentang pustaka Microsoft.Extensions.Primitives . Primitif dalam artikel ini tidak akan dikacaukan dengan jenis primitif .NET dari BCL, atau bahasa C#. Sebagai gantinya, jenis dalam pustaka primitif berfungsi sebagai blok penyusun untuk beberapa paket NuGet .NET periferal, seperti:

Mengubah pemberitahuan

Menyebarkan pemberitahuan ketika perubahan terjadi adalah konsep dasar dalam pemrograman. Status objek yang diamati lebih sering daripada tidak dapat berubah. Ketika perubahan terjadi, implementasi antarmuka dapat digunakan untuk memberi tahu pihak yang tertarik tentang perubahan tersebut Microsoft.Extensions.Primitives.IChangeToken . Implementasi yang tersedia adalah sebagai berikut:

Sebagai pengembang, Anda juga bebas untuk mengimplementasikan jenis Anda sendiri. Antarmuka IChangeToken mendefinisikan beberapa properti:

  • IChangeToken.HasChanged: Mendapatkan nilai yang menunjukkan apakah perubahan telah terjadi.
  • IChangeToken.ActiveChangeCallbacks: Menunjukkan apakah token akan secara proaktif menaikkan panggilan balik. Jika false, konsumen token harus melakukan polling HasChanged untuk mendeteksi perubahan.

Fungsionalitas berbasis instans

Pertimbangkan contoh penggunaan berikut dari 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

Dalam contoh sebelumnya, CancellationTokenSource dibuat dan Token diteruskan ke CancellationChangeToken konstruktor. Status awal HasChanged ditulis ke konsol. Action<object?> callback Dibuat yang menulis saat panggilan balik dipanggil ke konsol. Metode token RegisterChangeCallback(Action<Object>, Object) dipanggil, mengingat callback. using Dalam pernyataan, cancellationTokenSource dibatalkan. Ini memicu panggilan balik, dan statusnya HasChanged lagi ditulis ke konsol.

Ketika Anda perlu mengambil tindakan dari beberapa sumber perubahan, gunakan CompositeChangeToken. Implementasi ini menggabungkan satu atau beberapa token perubahan dan menembakkan setiap panggilan balik terdaftar tepat satu kali terlepas dari berapa kali perubahan dipicu. Pertimbangkan contoh berikut:

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.

Dalam kode C# sebelumnya, tiga CancellationTokenSource instans objek dibuat dan dipasangkan dengan instans yang CancellationChangeToken sesuai. Token komposit dibuat dengan meneruskan array token ke CompositeChangeToken konstruktor. Action<object?> callback dibuat, tetapi kali state ini objek digunakan dan ditulis ke konsol sebagai pesan yang diformat. Panggilan balik didaftarkan empat kali, masing-masing dengan argumen objek status yang sedikit berbeda. Kode ini menggunakan generator nomor pseudo-random untuk memilih salah satu sumber token perubahan (tidak masalah yang mana) dan memanggil metodenya Cancel() . Ini memicu perubahan, memanggil setiap panggilan balik terdaftar tepat sekali.

Pendekatan alternatif static

Sebagai alternatif untuk memanggil RegisterChangeCallback, Anda dapat menggunakan Microsoft.Extensions.Primitives.ChangeToken kelas statis. Pertimbangkan pola konsumsi berikut:

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.

Sama seperti contoh sebelumnya, Anda memerlukan implementasi IChangeToken yang diproduksi oleh changeTokenProducer. Produsen didefinisikan sebagai dan Func<IChangeToken> diharapkan bahwa ini akan mengembalikan token baru setiap pemanggilan. consumer adalah Action saat tidak menggunakan state, atau Action<TState> tempat jenis TState generik mengalir melalui pemberitahuan perubahan.

Tokenizer string, segmen, dan nilai

Berinteraksi dengan string adalah hal biasa dalam pengembangan aplikasi. Berbagai representasi string diurai, dipisahkan, atau diulang. Pustaka primitif menawarkan beberapa jenis pilihan yang membantu membuat interaksi dengan string lebih dioptimalkan dan efisien. Pertimbangkan jenis berikut:

  • StringSegment: Representasi substring yang dioptimalkan.
  • StringTokenizer: Tokenisasi string ke dalam StringSegment instans.
  • StringValues: Mewakili null, nol, satu, atau banyak string dengan cara yang efisien.

Jenis StringSegment

Di bagian ini, Anda akan mempelajari representasi substring yang dioptimalkan yang dikenal sebagai jenisnya StringSegment struct . Pertimbangkan contoh kode C# berikut yang menunjukkan beberapa StringSegment properti dan metode :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 "

Kode sebelumnya membuat instans StringSegment nilai yang string diberikan, offset, dan length. StringSegment.Buffer adalah argumen string asli, dan StringSegment.Value adalah substring berdasarkan nilai StringSegment.Offset dan StringSegment.Length .

Struktur StringSegment ini menyediakan banyak metode untuk berinteraksi dengan segmen .

Jenis StringTokenizer

Objek StringTokenizer adalah jenis struct yang tokenisasi string ke dalam StringSegment instans. Tokenisasi string besar biasanya melibatkan pemisahan string terpisah dan iterasi di atasnya. Dengan yang mengatakan, String.Split mungkin datang ke pikiran. API ini serupa, tetapi secara umum, StringTokenizer memberikan performa yang lebih baik. Pertama, pertimbangkan contoh berikut:

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

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

Dalam kode sebelumnya, instans jenis StringTokenizer dibuat dengan 900 paragraf Lorem Ipsum teks yang dihasilkan secara otomatis dan array dengan satu nilai karakter ' 'spasi putih . Setiap nilai dalam tokenizer direpresentasikan sebagai StringSegment. Kode iterasi segmen, memungkinkan konsumen untuk berinteraksi dengan masing-masing segment.

Tolok ukur dibandingkan dengan StringTokenizerstring.Split

Dengan berbagai cara mengiris dan membelah string, rasanya tepat untuk membandingkan dua metode dengan tolok ukur. Menggunakan paket BenchmarkDotNet NuGet, pertimbangkan dua metode tolok ukur berikut:

  1. Menggunakan StringTokenizer:

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

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

Kedua metode terlihat mirip pada area permukaan API, dan keduanya mampu membagi string besar menjadi gugus. Hasil tolok ukur di bawah ini menunjukkan bahwa StringTokenizer pendekatannya hampir tiga kali lebih cepat, tetapi hasilnya dapat bervariasi. Seperti semua pertimbangan performa, Anda harus mengevaluasi kasus penggunaan spesifik Anda.

Metode Rata-rata Kesalahan StdDev Rasio
Pembuat token 3,315 mdtk 0,0659 ms 0,0705 ms 0,32
Split 10,257 mdtk 0,2018 ms 0,2552 mdtk 1

Legenda

  • Rata-rata: Rata-rata aritmatika dari semua pengukuran
  • Kesalahan: Setengah dari interval keyakinan 99,9%
  • Simpang siur standar: Simpang siur standar dari semua pengukuran
  • Median: Nilai yang memisahkan bagian yang lebih tinggi dari semua pengukuran (persentil ke-50)
  • Rasio: Rata-rata distribusi rasio (Saat Ini/Garis Besar)
  • Deviasi standar rasio: Simpanpan baku distribusi rasio (Saat Ini/Garis Besar)
  • 1 mdtk: 1 Milidetik (0,001 detik)

Untuk informasi selengkapnya tentang tolok ukur dengan .NET, lihat BenchmarkDotNet.

Jenis StringValues

Objek StringValues adalah struct jenis yang mewakili null, nol, satu, atau banyak string dengan cara yang efisien. Jenis dapat StringValues dibangun dengan salah satu sintaks berikut: string? atau string?[]?. Menggunakan teks dari contoh sebelumnya, pertimbangkan kode C# berikut:

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

Kode sebelumnya membuat instans objek yang StringValues diberikan array nilai string. StringValues.Count ditulis ke konsol.

Jenisnya StringValues adalah implementasi dari jenis koleksi berikut:

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

Dengan demikian, itu dapat diulang dan masing-masing value dapat berinteraksi sesuai kebutuhan.

Lihat juga