Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Dalam tutorial ini, Anda mempelajari cara:
- Menerapkan pola pengendali interpolasi string
- Berinteraksi dengan penerima dalam operasi interpolasi string.
- Tambahkan argumen ke handler interpolasi string
- Memahami fitur pustaka baru untuk interpolasi string
Prasyarat
Anda perlu menyiapkan komputer Anda untuk menjalankan .NET. Pengkompilasi C# tersedia dengan
Tutorial ini mengasumsikan Anda terbiasa dengan C# dan .NET, termasuk Visual Studio atau .NET CLI.
Anda dapat menulis handler kustom untuk string terinterpolasi . Handler string interpolasi adalah tipe yang memproses ekspresi placeholder dalam string interpolasi. Tanpa pengelola kustom, pengganti diproses dengan cara yang mirip dengan String.Format. Setiap placeholder diformat sebagai teks, kemudian komponen digabungkan untuk membentuk string hasilnya.
Anda dapat menulis handler untuk skenario apa pun di mana Anda menggunakan informasi tentang string yang dihasilkan. Apakah akan digunakan? Batasan apa yang ada dalam format? Beberapa contohnya meliputi:
- Anda mungkin memerlukan tidak ada string yang dihasilkan yang lebih besar dari beberapa batas, seperti 80 karakter. Anda dapat memproses string terinterpolasi untuk mengisi buffer panjang tetap, dan berhenti memproses setelah panjang buffer tercapai.
- Anda mungkin menggunakan format tabular, dan setiap placeholder harus memiliki panjang tetap. Handler kustom dapat memberlakukan itu, daripada memaksa semua kode klien agar sesuai.
Dalam tutorial ini, Anda membuat handler interpolasi string untuk salah satu skenario performa inti: pengelogan pustaka. Bergantung pada tingkat log yang dikonfigurasi, pekerjaan untuk membuat pesan log tidak diperlukan. Jika pengelogan nonaktif, pekerjaan untuk membuat string dari ekspresi string terinterpolasi tidak diperlukan. Pesan tidak pernah dicetak, sehingga perangkaian string apa pun dapat dilewati. Selain itu, ekspresi dalam placeholder, termasuk pembuatan jejak tumpukan, tidak perlu dilakukan.
Handler string terinterpolasi dapat menentukan apakah string yang diformat akan digunakan, dan hanya melakukan pekerjaan yang diperlukan jika diperlukan.
Implementasi awal
Mari kita mulai dari kelas Logger dasar yang mendukung tingkat yang berbeda:
public enum LogLevel
{
Off,
Critical,
Error,
Warning,
Information,
Trace
}
public class Logger
{
public LogLevel EnabledLevel { get; init; } = LogLevel.Error;
public void LogMessage(LogLevel level, string msg)
{
if (EnabledLevel < level) return;
Console.WriteLine(msg);
}
}
Logger ini mendukung enam tingkat yang berbeda. Saat pesan tidak meneruskan filter tingkat log, tidak ada output. API publik untuk pencatat menerima string (sepenuhnya diformat) sebagai pesan. Semua pekerjaan untuk membuat string telah dilakukan.
Menerapkan pola handler
Langkah ini untuk membangun handler string terinterpolasi yang membuat ulang perilaku saat ini. Handler string terinterpolasi adalah jenis yang harus memiliki karakteristik berikut:
- System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute diterapkan pada tipe.
- Konstruktor yang memiliki dua parameter
int,literalLengthdanformattedCount. (Parameter lainnya diizinkan). - Metode
AppendLiteralpublik dengan tanda tangan:public void AppendLiteral(string s). - Metode
AppendFormattedpublik generik dengan tanda tangan:public void AppendFormatted<T>(T t).
Secara internal, penyusun membuat string yang diformat, dan menyediakan anggota bagi klien untuk mengambil string tersebut. Kode berikut menunjukkan jenis LogInterpolatedStringHandler yang memenuhi persyaratan ini:
[InterpolatedStringHandler]
public struct LogInterpolatedStringHandler
{
// Storage for the built-up string
StringBuilder builder;
public LogInterpolatedStringHandler(int literalLength, int formattedCount)
{
builder = new StringBuilder(literalLength);
Console.WriteLine($"\tliteral length: {literalLength}, formattedCount: {formattedCount}");
}
public void AppendLiteral(string s)
{
Console.WriteLine($"\tAppendLiteral called: {{{s}}}");
builder.Append(s);
Console.WriteLine($"\tAppended the literal string");
}
public void AppendFormatted<T>(T t)
{
Console.WriteLine($"\tAppendFormatted called: {{{t}}} is of type {typeof(T)}");
builder.Append(t?.ToString());
Console.WriteLine($"\tAppended the formatted object");
}
internal string GetFormattedText() => builder.ToString();
}
Sekarang Anda dapat menambahkan kelebihan beban ke LogMessage di kelas Logger untuk mencoba handler string terinterpolasi baru Anda:
public void LogMessage(LogLevel level, LogInterpolatedStringHandler builder)
{
if (EnabledLevel < level) return;
Console.WriteLine(builder.GetFormattedText());
}
Anda tidak perlu menghapus metode LogMessage asli, pengkompilasi lebih memilih metode dengan parameter handler terinterpolasi daripada metode dengan parameter string ketika argumen adalah ekspresi string terinterpolasi.
Anda dapat memverifikasi bahwa handler baru dipanggil menggunakan kode berikut sebagai program utama:
var logger = new Logger() { EnabledLevel = LogLevel.Warning };
var time = DateTime.Now;
logger.LogMessage(LogLevel.Error, $"Error Level. CurrentTime: {time}. This is an error. It will be printed.");
logger.LogMessage(LogLevel.Trace, $"Trace Level. CurrentTime: {time}. This won't be printed.");
logger.LogMessage(LogLevel.Warning, "Warning Level. This warning is a string, not an interpolated string expression.");
Menjalankan aplikasi menghasilkan output yang mirip dengan teks berikut:
literal length: 65, formattedCount: 1
AppendLiteral called: {Error Level. CurrentTime: }
Appended the literal string
AppendFormatted called: {10/20/2021 12:19:10 PM} is of type System.DateTime
Appended the formatted object
AppendLiteral called: {. This is an error. It will be printed.}
Appended the literal string
Error Level. CurrentTime: 10/20/2021 12:19:10 PM. This is an error. It will be printed.
literal length: 50, formattedCount: 1
AppendLiteral called: {Trace Level. CurrentTime: }
Appended the literal string
AppendFormatted called: {10/20/2021 12:19:10 PM} is of type System.DateTime
Appended the formatted object
AppendLiteral called: {. This won't be printed.}
Appended the literal string
Warning Level. This warning is a string, not an interpolated string expression.
Menelusuri output, Anda dapat melihat bagaimana pengkompilasi menambahkan kode untuk memanggil handler dan membangun string:
- Pengompilasi menambahkan panggilan untuk membangun handler, dengan meneruskan panjang total teks literal dalam string format, dan jumlah tempat penampung.
- Pengkompilasi menambahkan fungsi panggilan ke
AppendLiteraldanAppendFormatteduntuk setiap bagian string literal dan untuk setiap pengganti. - Pengkompilasi memanggil metode
LogMessagemenggunakanCoreInterpolatedStringHandlersebagai argumen.
Terakhir, perhatikan bahwa peringatan terakhir tidak memanggil handler string terinterpolasi. Argumen adalah string, sehingga panggilan memanggil kelebihan beban lain dengan parameter string.
Penting
Gunakan ref struct untuk penanganan string terinterpolasi hanya jika benar-benar diperlukan. Menggunakan ref struct akan memiliki batasan karena harus disimpan di tumpukan. Misalnya, mereka tidak akan berfungsi jika lubang string terinterpolasi berisi ekspresi await karena pengkompilasi perlu menyimpan handler dalam implementasi IAsyncStateMachine yang dihasilkan kompilator.
Menambahkan lebih banyak kemampuan ke handler
Versi sebelumnya dari handler string terinterpolasi mengimplementasikan pola. Untuk menghindari pemrosesan setiap ekspresi tempat penampung, Anda memerlukan informasi lebih lanjut di handler. Di bagian ini, Anda meningkatkan handler sehingga tidak terlalu berfungsi ketika string yang dibuat tidak ditulis ke log. Anda menggunakan System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute untuk menentukan pemetaan antara parameter dari API publik dan parameter dari konstruktor handler. Yang menyediakan handler dengan informasi yang diperlukan untuk menentukan apakah string yang diinterpolasi harus dievaluasi.
Mari kita mulai dengan perubahan pada Handler. Pertama, tambahkan bidang untuk melacak apakah handler diaktifkan. Tambahkan dua parameter ke konstruktor: satu untuk menentukan tingkat log untuk pesan ini, dan yang lainnya referensi ke objek log:
private readonly bool enabled;
public LogInterpolatedStringHandler(int literalLength, int formattedCount, Logger logger, LogLevel logLevel)
{
enabled = logger.EnabledLevel >= logLevel;
builder = new StringBuilder(literalLength);
Console.WriteLine($"\tliteral length: {literalLength}, formattedCount: {formattedCount}");
}
Selanjutnya, gunakan bidang sehingga handler Anda hanya menambahkan literal atau objek yang diformat saat string akhir akan digunakan:
public void AppendLiteral(string s)
{
Console.WriteLine($"\tAppendLiteral called: {{{s}}}");
if (!enabled) return;
builder.Append(s);
Console.WriteLine($"\tAppended the literal string");
}
public void AppendFormatted<T>(T t)
{
Console.WriteLine($"\tAppendFormatted called: {{{t}}} is of type {typeof(T)}");
if (!enabled) return;
builder.Append(t?.ToString());
Console.WriteLine($"\tAppended the formatted object");
}
Selanjutnya, Anda perlu memperbarui deklarasi LogMessage sehingga kompilator meneruskan parameter tambahan ke konstruktor handler. Itu ditangani menggunakan System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute pada argumen handler:
public void LogMessage(LogLevel level, [InterpolatedStringHandlerArgument("", "level")] LogInterpolatedStringHandler builder)
{
if (EnabledLevel < level) return;
Console.WriteLine(builder.GetFormattedText());
}
Atribut ini menentukan daftar argumen untuk LogMessage yang memetakan ke parameter yang mengikuti parameter literalLength dan formattedCount yang diperlukan. String kosong (""), menentukan penerima. Pengkompilasi menggantikan nilai objek Logger yang diwakili oleh this untuk argumen berikutnya ke konstruktor handler. Pengkompilasi menggantikan nilai level untuk argumen berikut. Anda dapat memberikan berapa pun jumlah argumen untuk setiap handler yang Anda buat. Argumen yang Anda tambahkan adalah argumen string.
Anda dapat menjalankan versi ini menggunakan kode pengujian yang sama. Kali ini, Anda akan melihat hasil berikut:
literal length: 65, formattedCount: 1
AppendLiteral called: {Error Level. CurrentTime: }
Appended the literal string
AppendFormatted called: {10/20/2021 12:19:10 PM} is of type System.DateTime
Appended the formatted object
AppendLiteral called: {. This is an error. It will be printed.}
Appended the literal string
Error Level. CurrentTime: 10/20/2021 12:19:10 PM. This is an error. It will be printed.
literal length: 50, formattedCount: 1
AppendLiteral called: {Trace Level. CurrentTime: }
AppendFormatted called: {10/20/2021 12:19:10 PM} is of type System.DateTime
AppendLiteral called: {. This won't be printed.}
Warning Level. This warning is a string, not an interpolated string expression.
Anda dapat melihat bahwa metode AppendLiteral dan AppendFormat sedang dipanggil, tetapi tidak melakukan pekerjaan apa pun. Handler menentukan bahwa string akhir tidak diperlukan, sehingga handler tidak membangunnya. Masih ada beberapa perbaikan yang harus dilakukan.
Pertama, Anda dapat menambahkan kelebihan AppendFormatted yang membatasi argumen ke jenis yang mengimplementasikan System.IFormattable. Kelebihan beban ini memungkinkan penelepon untuk menambahkan string format di tempat penampung. Saat membuat perubahan ini, mari kita ubah juga jenis pengembalian metode AppendFormatted dan AppendLiteral lainnya, dari void ke bool (jika salah satu metode ini memiliki jenis pengembalian yang berbeda, maka Anda mendapatkan kesalahan kompilasi). Perubahan itu memungkinkan terhubung singkat. Metode mengembalikan false untuk menunjukkan bahwa pemrosesan ekspresi string terinterpolasi harus dihentikan. Mengembalikan true menunjukkan bahwa proses harus dilanjutkan. Dalam contoh ini, Anda menggunakannya untuk menghentikan pemrosesan saat string yang dihasilkan tidak diperlukan. Sirkuit pendek mendukung tindakan yang lebih halus. Anda dapat berhenti memproses ekspresi setelah mencapai panjang tertentu, untuk mendukung buffer panjang tetap. Atau beberapa kondisi dapat menunjukkan elemen yang tersisa tidak diperlukan.
public void AppendFormatted<T>(T t, string format) where T : IFormattable
{
Console.WriteLine($"\tAppendFormatted (IFormattable version) called: {t} with format {{{format}}} is of type {typeof(T)},");
builder.Append(t?.ToString(format, null));
Console.WriteLine($"\tAppended the formatted object");
}
Dengan tambahan itu, Anda dapat menentukan string format dalam ekspresi string terinterpolasi Anda:
var time = DateTime.Now;
logger.LogMessage(LogLevel.Error, $"Error Level. CurrentTime: {time}. The time doesn't use formatting.");
logger.LogMessage(LogLevel.Error, $"Error Level. CurrentTime: {time:t}. This is an error. It will be printed.");
logger.LogMessage(LogLevel.Trace, $"Trace Level. CurrentTime: {time:t}. This won't be printed.");
:t pada pesan pertama menentukan "format waktu singkat" untuk waktu saat ini. Contoh sebelumnya menunjukkan salah satu kelebihan beban ke metode AppendFormatted yang dapat Anda buat untuk handler Anda. Anda tidak perlu menentukan argumen generik untuk objek yang sedang diformat. Anda mungkin memiliki cara yang lebih efisien untuk mengonversi jenis yang Anda buat ke string. Anda dapat menulis overload AppendFormatted yang menerima tipe tersebut alih-alih argumen generik. Pengkompilasi memilih kelebihan beban terbaik. Runtime menggunakan teknik ini untuk mengonversi System.Span<T> ke output string. Anda dapat menambahkan parameter bilangan bulat untuk menentukan perataan output, dengan atau tanpa IFormattable.
System.Runtime.CompilerServices.DefaultInterpolatedStringHandler yang dikirim dengan .NET 6 berisi sembilan kelebihan beban AppendFormatted untuk penggunaan yang berbeda. Anda dapat menggunakannya sebagai referensi saat membangun handler untuk tujuan Anda.
Jalankan sampel sekarang, dan Anda melihat bahwa untuk pesan Trace, hanya AppendLiteral pertama yang dipanggil:
literal length: 60, formattedCount: 1
AppendLiteral called: Error Level. CurrentTime:
Appended the literal string
AppendFormatted called: 10/20/2021 12:18:29 PM is of type System.DateTime
Appended the formatted object
AppendLiteral called: . The time doesn't use formatting.
Appended the literal string
Error Level. CurrentTime: 10/20/2021 12:18:29 PM. The time doesn't use formatting.
literal length: 65, formattedCount: 1
AppendLiteral called: Error Level. CurrentTime:
Appended the literal string
AppendFormatted (IFormattable version) called: 10/20/2021 12:18:29 PM with format {t} is of type System.DateTime,
Appended the formatted object
AppendLiteral called: . This is an error. It will be printed.
Appended the literal string
Error Level. CurrentTime: 12:18 PM. This is an error. It will be printed.
literal length: 50, formattedCount: 1
AppendLiteral called: Trace Level. CurrentTime:
Warning Level. This warning is a string, not an interpolated string expression.
Anda dapat melakukan pembaruan terakhir pada konstruktor handler yang meningkatkan efisiensi. Handler dapat menambahkan parameter out bool akhir. Mengatur parameter tersebut ke false menunjukkan bahwa handler tidak boleh dipanggil sama sekali untuk memproses ekspresi string terinterpolasi:
public LogInterpolatedStringHandler(int literalLength, int formattedCount, Logger logger, LogLevel level, out bool isEnabled)
{
isEnabled = logger.EnabledLevel >= level;
Console.WriteLine($"\tliteral length: {literalLength}, formattedCount: {formattedCount}");
builder = isEnabled ? new StringBuilder(literalLength) : default!;
}
Perubahan itu berarti Anda dapat menghapus bidang enabled. Kemudian, Anda dapat mengubah jenis pengembalian AppendLiteral dan AppendFormatted menjadi void.
Sekarang, saat Menjalankan sampel, Anda akan melihat output berikut:
literal length: 60, formattedCount: 1
AppendLiteral called: Error Level. CurrentTime:
Appended the literal string
AppendFormatted called: 10/20/2021 12:19:10 PM is of type System.DateTime
Appended the formatted object
AppendLiteral called: . The time doesn't use formatting.
Appended the literal string
Error Level. CurrentTime: 10/20/2021 12:19:10 PM. The time doesn't use formatting.
literal length: 65, formattedCount: 1
AppendLiteral called: Error Level. CurrentTime:
Appended the literal string
AppendFormatted (IFormattable version) called: 10/20/2021 12:19:10 PM with format {t} is of type System.DateTime,
Appended the formatted object
AppendLiteral called: . This is an error. It will be printed.
Appended the literal string
Error Level. CurrentTime: 12:19 PM. This is an error. It will be printed.
literal length: 50, formattedCount: 1
Warning Level. This warning is a string, not an interpolated string expression.
Satu-satunya output ketika LogLevel.Trace ditentukan adalah output dari konstruktor. Handler menunjukkan bahwa tidak diaktifkan, sehingga tidak ada metode Append yang dipanggil.
Contoh ini menggambarkan titik penting untuk penanganan string terinterpolasi, terutama ketika pustaka pengelogan digunakan. Efek samping apa pun pada placeholder mungkin tidak akan terjadi. Tambahkan kode berikut ke program utama Anda dan lihat perilaku ini beraksi:
int index = 0;
int numberOfIncrements = 0;
for (var level = LogLevel.Critical; level <= LogLevel.Trace; level++)
{
Console.WriteLine(level);
logger.LogMessage(level, $"{level}: Increment index a few times {index++}, {index++}, {index++}, {index++}, {index++}");
numberOfIncrements += 5;
}
Console.WriteLine($"Value of index {index}, value of numberOfIncrements: {numberOfIncrements}");
Anda dapat melihat variabel index ditingkatkan lima kali setiap iterasi. Karena tempat penampung dievaluasi hanya untuk tingkat Critical, Error dan Warning, bukan untuk Information dan Trace, nilai akhir index tidak sesuai dengan harapan:
Critical
Critical: Increment index a few times 0, 1, 2, 3, 4
Error
Error: Increment index a few times 5, 6, 7, 8, 9
Warning
Warning: Increment index a few times 10, 11, 12, 13, 14
Information
Trace
Value of index 15, value of numberOfIncrements: 25
Handler string terinterpolasi memberikan kontrol yang lebih besar atas bagaimana ekspresi string terinterpolasi dikonversi menjadi string. Tim runtime .NET menggunakan fitur ini untuk meningkatkan performa di beberapa area. Anda dapat menggunakan kemampuan yang sama di pustaka Anda sendiri. Untuk menjelajahi lebih jauh, lihat System.Runtime.CompilerServices.DefaultInterpolatedStringHandler. Ini menyediakan implementasi yang lebih lengkap daripada yang Anda bangun di sini. Anda melihat lebih banyak kelebihan beban yang dimungkinkan untuk metode Append.