Bagikan melalui


Primitif: Pustaka ekstensi untuk .NET

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

Mengubah pemberitahuan

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

Sebagai pengembang, Anda juga bebas untuk mengimplementasikan tipe 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 konstruktor CancellationChangeToken. Status awal HasChanged ditulis ke konsol. Action<object?> callback dibuat yang akan menulis ke konsol ketika callback dipanggil. Metode token RegisterChangeCallback(Action<Object>, Object) dipanggil, diberikan callback. Di dalam using pernyataan, cancellationTokenSource dibatalkan. Ini memicu panggilan balik, dan status dari HasChanged kembali 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 instance objek CancellationTokenSource dibuat dan dipasangkan dengan instance CancellationChangeToken yang sesuai. Token komposit dibuat dengan mengoper array token ke konstruktor CompositeChangeToken. Action<object?> callback dibuat, tetapi kali ini objek state 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, menjalankan setiap callback yang terdaftar tepat satu kali.

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 Func<IChangeToken> dan diharapkan ini akan mengembalikan token baru pada setiap pemanggilan. consumer adalah Action ketika tidak menggunakan state, atau Action<TState> di mana tipe 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: Men-tokenisasi string menjadi instance StringSegment.
  • 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 StringSegmentstruct . Pertimbangkan contoh kode C# berikut yang menunjukkan beberapa properti StringSegment 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 menginstansiasi StringSegment dengan nilai string, sebuah 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 men-tokenisasi string ke dalam instans StringSegment. Tokenisasi string besar biasanya melibatkan pemisahan string dan melakukan iterasi atasnya. Dengan demikian, String.Split mungkin terlintas dalam 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, sebuah instance dari jenis StringTokenizer dibuat dengan 900 paragraf teks Lorem Ipsum yang dihasilkan secara otomatis dan array dengan satu nilai karakter ' ' spasi putih. Setiap nilai dalam tokenizer direpresentasikan sebagai StringSegment. Kode mengulangi segmen-segmen, dengan memungkinkan pengguna untuk berinteraksi dengan masing-masing segment.

Pembandingan antara StringTokenizer dengan string.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 ms 0,0659 ms 0,0705 ms 0,32
Belah 10,257 milidetik 0,2018 ms 0,2552 ms 1.00

Legenda

  • Rata-rata: Rata-rata aritmatika dari semua pengukuran
  • Kesalahan: Nilai setengah dari interval kepercayaan 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: Simpangan baku distribusi rasio (Saat Ini/Patokan)
  • 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 StringValues dapat dibangun dengan menggunakan 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 StringValues dengan array nilai string. StringValues.Count ditulis ke konsol.

Tipe StringValues ini merupakan implementasi dari tipe 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