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:
Microsoft.Extensions.Configuration
Microsoft.Extensions.Configuration.FileExtensions
Microsoft.Extensions.FileProviders.Composite
Microsoft.Extensions.FileProviders.Physical
Microsoft.Extensions.Logging.EventSource
Microsoft.Extensions.Options
System.Text.Json
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 pollingHasChanged
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 dalamStringSegment
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 StringTokenizer
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:
Menggunakan StringTokenizer:
StringBuilder buffer = new(); var tokenizer = new StringTokenizer( s_nineHundredAutoGeneratedParagraphsOfLoremIpsum, new[] { ' ', '.' }); foreach (StringSegment segment in tokenizer) { buffer.Append(segment.Value); }
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.