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.
Nota
Artikel ini adalah spesifikasi fitur. Spesifikasi berfungsi sebagai dokumen desain untuk fitur tersebut. Ini termasuk perubahan spesifikasi yang diusulkan, bersama dengan informasi yang diperlukan selama desain dan pengembangan fitur. Artikel ini diterbitkan sampai perubahan spesifikasi yang diusulkan diselesaikan dan dimasukkan dalam spesifikasi ECMA saat ini.
Mungkin ada beberapa perbedaan antara spesifikasi fitur dan implementasi yang selesai. Perbedaan tersebut tertuang dalam catatan rapat desain bahasa (LDM) terkait .
Anda dapat mempelajari lebih lanjut tentang proses untuk mengadopsi speklet fitur ke dalam standar bahasa C# dalam artikel tentang spesifikasi .
Masalah juara: https://github.com/dotnet/csharplang/issues/4487
Ringkasan
Kami memperkenalkan pola baru untuk membuat dan menggunakan ekspresi string terinterpolasi untuk memungkinkan pemformatan dan penggunaan yang efisien dalam skenario string umum dan skenario yang lebih khusus seperti kerangka kerja pengelogan, tanpa menimbulkan alokasi yang tidak perlu dari memformat string dalam kerangka kerja.
Motivasi
Hari ini, interpolasi string terutama berujung pada panggilan ke string.Format. Ini, sementara tujuan umum, dapat tidak efisien karena sejumlah alasan:
- Ini kotak argumen struktur apa pun, kecuali runtime telah terjadi untuk memperkenalkan kelebihan
string.Formatyang mengambil jenis argumen yang benar dalam urutan yang tepat.- Inilah alasan mengapa runtime ragu-ragu memperkenalkan versi generik dari metode tersebut, karena hal ini dapat menyebabkan ledakan kombinatorik pada instansiasi generik dari metode yang sangat umum.
- Itu harus mengalokasikan array untuk argumen dalam sebagian besar kasus.
- Tidak ada kesempatan untuk menghindari membuat instans jika tidak diperlukan. Kerangka kerja pengelogan, misalnya, akan merekomendasikan untuk menghindari interpolasi string karena akan menyebabkan string direalisasikan yang mungkin tidak diperlukan, tergantung pada tingkat log aplikasi saat ini.
- Ini tidak pernah dapat menggunakan
Spanatau jenis struct ref lainnya saat ini, karena struktur ref tidak diizinkan sebagai parameter jenis generik, yang berarti bahwa jika pengguna ingin menghindari penyalinan ke lokasi perantara, mereka harus memformat string secara manual.
Secara internal, runtime memiliki jenis yang disebut ValueStringBuilder untuk membantu menangani 2 skenario pertama ini. Mereka meneruskan buffer stackalloc'd ke penyusun, berulang kali memanggil AppendFormat dengan setiap bagian, dan kemudian mengeluarkan string akhir. Jika string yang dihasilkan melewati batas buffer tumpukan, string tersebut kemudian dapat berpindah ke array pada tumpukan. Namun, jenis ini berbahaya untuk diekspos secara langsung, karena penggunaan yang salah dapat menyebabkan array yang disewakan dibuang dua kali, yang kemudian akan menyebabkan semua jenis perilaku yang tidak ditentukan dalam program karena dua lokasi berpikir mereka memiliki akses satu-satunya ke array yang disewakan. Proposal ini menciptakan cara untuk menggunakan jenis ini dengan aman dari kode C# asli dengan hanya menulis string terinterpolasi literal, membiarkan kode tertulis tidak berubah sambil meningkatkan setiap string terinterpolasi yang ditulis pengguna. Ini juga memperluas pola ini untuk memungkinkan string terinterpolasi diteruskan sebagai argumen ke metode lain untuk menggunakan pola handler, yang didefinisikan oleh penerima metode, yang akan memungkinkan hal-hal seperti kerangka kerja pengelogan untuk menghindari alokasi string yang tidak akan pernah diperlukan, dan memberi pengguna C# sintaks interpolasi yang akrab dan nyaman.
Desain Terperinci
Pola handler
Kami memperkenalkan pola handler baru yang dapat mewakili string terinterpolasi yang diteruskan sebagai argumen ke metode. Bahasa Inggris sederhana dari pola ini adalah sebagai berikut:
Ketika interpolated_string_expression diteruskan sebagai argumen ke metode, kita memeriksa tipe parameternya. Jika jenis parameter memiliki konstruktor yang bisa dipanggil dengan 2 parameter int, literalLength dan formattedCount, ada kemungkinan mengambil parameter tambahan yang ditentukan oleh atribut pada parameter asli, ada kemungkinan memiliki parameter boolean yang mengikuti, dan jenis parameter asli memiliki metode instans AppendLiteral dan AppendFormatted yang bisa dipanggil untuk setiap bagian string terinterpolasi, kemudian kita menurunkan interpolasi dengan menggunakan itu, alih-alih menjadi panggilan tradisional ke string.Format(formatStr, args). Contoh yang lebih konkret sangat membantu untuk menggambarkan hal ini:
// The handler that will actually "build" the interpolated string"
[InterpolatedStringHandler]
public ref struct TraceLoggerParamsInterpolatedStringHandler
{
// Storage for the built-up string
private bool _logLevelEnabled;
public TraceLoggerParamsInterpolatedStringHandler(int literalLength, int formattedCount, Logger logger, out bool handlerIsValid)
{
if (!logger._logLevelEnabled)
{
handlerIsValid = false;
return;
}
handlerIsValid = true;
_logLevelEnabled = logger.EnabledLevel;
}
public void AppendLiteral(string s)
{
// Store and format part as required
}
public void AppendFormatted<T>(T t)
{
// Store and format part as required
}
}
// The logger class. The user has an instance of this, accesses it via static state, or some other access
// mechanism
public class Logger
{
// Initialization code omitted
public LogLevel EnabledLevel;
public void LogTrace([InterpolatedStringHandlerArguments("")]TraceLoggerParamsInterpolatedStringHandler handler)
{
// Impl of logging
}
}
Logger logger = GetLogger(LogLevel.Info);
// Given the above definitions, usage looks like this:
var name = "Fred Silberberg";
logger.LogTrace($"{name} will never be printed because info is < trace!");
// This is converted to:
var name = "Fred Silberberg";
var receiverTemp = logger;
var handler = new TraceLoggerParamsInterpolatedStringHandler(literalLength: 47, formattedCount: 1, receiverTemp, out var handlerIsValid);
if (handlerIsValid)
{
handler.AppendFormatted(name);
handler.AppendLiteral(" will never be printed because info is < trace!");
}
receiverTemp.LogTrace(handler);
Di sini, karena TraceLoggerParamsInterpolatedStringHandler memiliki konstruktor dengan parameter yang benar, kami mengatakan bahwa string terinterpolasi memiliki konversi handler implisit ke parameter tersebut, dan menurunkan ke pola yang ditunjukkan di atas. Spesifikasi yang diperlukan untuk ini agak rumit, dan diperluas di bawah ini.
Sisa proposal ini akan menggunakan Append... untuk merujuk ke salah satu AppendLiteral atau AppendFormatted dalam kasus ketika keduanya berlaku.
Atribut baru
Pengkompilasi mengenali System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute:
using System;
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
public sealed class InterpolatedStringHandlerAttribute : Attribute
{
public InterpolatedStringHandlerAttribute()
{
}
}
}
Atribut ini digunakan oleh pengkompilasi untuk menentukan apakah jenis adalah jenis handler string terinterpolasi yang valid.
Pengkompilasi juga mengenali System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class InterpolatedStringHandlerArgumentAttribute : Attribute
{
public InterpolatedHandlerArgumentAttribute(string argument);
public InterpolatedHandlerArgumentAttribute(params string[] arguments);
public string[] Arguments { get; }
}
}
Atribut ini digunakan pada parameter, untuk memberi tahu pengkompilasi cara menurunkan pola handler string terinterpolasi yang digunakan dalam posisi parameter.
Konversi pengendali string yang diinterpolasi
Jenis T dianggap sebagai applicable_interpolated_string_handler_type jika diberikan atribut System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute.
Ada interpolated_string_handler_conversion implisit untuk T dari interpolated_string_expression, atau additive_expression yang sepenuhnya terdiri dari _interpolated_string_expression_s dan hanya menggunakan operator +.
Untuk kemudahan di sisa spesifikasi kecil ini, interpolated_string_expression mengacu pada interpolated_string_expressionsederhana, dan juga pada additive_expression yang sepenuhnya terdiri dari _interpolated_string_expression_s dan hanya menggunakan operator +.
Perhatikan bahwa konversi ini selalu ada, terlepas dari apakah akan ada kesalahan nanti ketika benar-benar mencoba menurunkan interpolasi menggunakan pola handler. Ini dilakukan untuk membantu memastikan bahwa ada kesalahan yang dapat diprediksi dan berguna dan perilaku runtime tidak berubah berdasarkan konten string yang diinterpolasi.
Penyesuaian anggota fungsi yang berlaku
Kami menyesuaikan kata-kata algoritma anggota fungsi yang berlaku (§12.6.4.2) sebagai berikut (sub-poin baru ditambahkan ke setiap bagian, dalam huruf tebal):
Anggota fungsi dikatakan sebagai anggota fungsi yang berlaku sehubungan dengan daftar argumen A ketika semua hal berikut ini benar:
- Setiap argumen dalam
Asesuai dengan parameter dalam deklarasi anggota fungsi seperti yang dijelaskan dalam parameter terkait (§12.6.2.2), dan parameter apa pun yang tidak ada argumen yang sesuai adalah parameter opsional. - Untuk setiap argumen pada
A, mode penerusan argumen (yaitu, nilai,ref, atauout) identik dengan mode pengoperasian parameter yang sesuai, dan- untuk parameter nilai atau array parameter, konversi implisit (§10.2) ada dari argumen ke tipe parameter yang sesuai, atau
-
untuk parameter
refyang tipe datanya adalah struct, terdapat interpolated_string_handler_conversion implisit dari argumen ke tipe parameter yang bersesuaian, atau - untuk parameter
refatauout, jenis argumen identik dengan jenis parameter yang sesuai. Lagipula, parameterrefatauoutadalah alias untuk argumen yang diteruskan.
Untuk anggota fungsi yang menyertakan array parameter, jika anggota fungsi berlaku oleh aturan di atas, dikatakan berlaku dalam bentuk normal . Jika anggota fungsi yang menyertakan array parameter tidak berlaku dalam bentuk normalnya, anggota fungsi dapat berlaku dalam formulir diperluas:
- Formulir yang diperluas dibangun dengan mengganti array parameter dalam deklarasi anggota fungsi dengan parameter nilai nol atau lebih dari jenis elemen array parameter sehingga jumlah argumen dalam daftar argumen
Acocok dengan jumlah total parameter. JikaAmemiliki lebih sedikit argumen daripada jumlah parameter tetap dalam deklarasi anggota fungsi, bentuk anggota fungsi yang diperluas tidak dapat dibangun dan dengan demikian tidak berlaku. - Jika tidak, bentuk yang diperluas berlaku jika mode penerusan parameter untuk setiap argumen dalam
Aidentik dengan mode penerusan parameter yang bersesuaian, dan- untuk parameter nilai tetap atau parameter nilai yang dibuat oleh ekspansi, konversi implisit (§10,2) ada dari jenis argumen ke jenis parameter yang sesuai, atau
-
untuk parameter
refyang tipe datanya adalah struct, terdapat interpolated_string_handler_conversion implisit dari argumen ke tipe parameter yang bersesuaian, atau - untuk parameter
refatauout, jenis argumen identik dengan jenis parameter yang sesuai.
Catatan penting: ini berarti bahwa jika ada 2 kelebihan beban yang setara, yang hanya berbeda dengan jenis applicable_interpolated_string_handler_type, kelebihan beban ini akan dianggap ambigu. Selanjutnya, karena kita tidak dapat melihat melalui pemaksaan eksplisit, ada kemungkinan bahwa mungkin ada skenario yang tidak dapat diselesaikan di mana kedua overload yang berlaku menggunakan InterpolatedStringHandlerArguments dan benar-benar tidak dapat dipanggil tanpa melakukan proses penurunan handler secara manual. Kita berpotensi membuat perubahan pada algoritma anggota fungsi yang lebih baik untuk menyelesaikan ini jika kita memilih, tetapi skenario ini tidak mungkin terjadi dan bukan prioritas untuk mengatasinya.
Konversi yang lebih baik dari penyesuaian ekspresi
Kami mengganti ekspresi pada bagian (§12.6.4.5) dengan konversi yang lebih baik sebagai berikut:
Mengingat C1 konversi implisit yang mengonversi dari ekspresi E ke jenis T1, dan C2 konversi implisit yang mengonversi dari ekspresi E ke jenis T2, C1 adalahkonversi yang lebih baikdaripada C2 jika:
-
Eadalah interpolated_string_expressionnon-konstan,C1adalah implicit_string_handler_conversion,T1adalah applicable_interpolated_string_handler_type, danC2bukan implicit_string_handler_conversion, atau -
Etidak sama persis denganT2dan setidaknya salah satu dari hal berikut berlaku:
Ini berarti bahwa ada beberapa aturan resolusi kelebihan beban yang berpotensi tidak jelas, tergantung pada apakah string terinterpolasi yang dimaksud adalah ekspresi konstan atau tidak. Misalnya:
void Log(string s) { ... }
void Log(TraceLoggerParamsInterpolatedStringHandler p) { ... }
Log($""); // Calls Log(string s), because $"" is a constant expression
Log($"{"test"}"); // Calls Log(string s), because $"{"test"}" is a constant expression
Log($"{1}"); // Calls Log(TraceLoggerParamsInterpolatedStringHandler p), because $"{1}" is not a constant expression
Ini diperkenalkan sehingga hal-hal yang dapat dipancarkan sebagai konstanta melakukannya, dan tidak menimbulkan overhead apa pun, sementara hal-hal yang tidak dapat menjadi konstanta menggunakan pola handler.
InterpolatedStringHandler dan Penggunaan
Kami memperkenalkan jenis baru di System.Runtime.CompilerServices: DefaultInterpolatedStringHandler. Ini adalah struktur ref dengan banyak semantik yang sama dengan ValueStringBuilder, yang ditujukan untuk penggunaan langsung oleh pengkompilasi C#. Struktur ini akan terlihat seperti ini:
// API Proposal issue: https://github.com/dotnet/runtime/issues/50601
namespace System.Runtime.CompilerServices
{
[InterpolatedStringHandler]
public ref struct DefaultInterpolatedStringHandler
{
public DefaultInterpolatedStringHandler(int literalLength, int formattedCount);
public string ToStringAndClear();
public void AppendLiteral(string value);
public void AppendFormatted<T>(T value);
public void AppendFormatted<T>(T value, string? format);
public void AppendFormatted<T>(T value, int alignment);
public void AppendFormatted<T>(T value, int alignment, string? format);
public void AppendFormatted(ReadOnlySpan<char> value);
public void AppendFormatted(ReadOnlySpan<char> value, int alignment = 0, string? format = null);
public void AppendFormatted(string? value);
public void AppendFormatted(string? value, int alignment = 0, string? format = null);
public void AppendFormatted(object? value, int alignment = 0, string? format = null);
}
}
Kami melakukan sedikit perubahan pada aturan mengenai arti dari interpolated_string_expression (§12.8.3):
Jika jenis string terinterpolasi string dan jenis System.Runtime.CompilerServices.DefaultInterpolatedStringHandler ada, dan konteks saat ini mendukung penggunaan jenis tersebut, stringditurunkan menggunakan pola handler. Nilai string akhir kemudian diperoleh dengan memanggil ToStringAndClear() pada jenis handler.Jika tidak, jika jenis string yang diinterpolasi System.IFormattable atau System.FormattableString [sisanya tidak berubah]
Aturan "dan konteks saat ini mendukung penggunaan jenis tersebut" sengaja tidak jelas untuk memberikan kelonggaran pengkompilasi dalam mengoptimalkan penggunaan pola ini. Jenis handler kemungkinan merupakan jenis struct ref, dan jenis struct ref biasanya tidak diizinkan dalam metode asinkron. Untuk kasus khusus ini, pengkompilasi akan diizinkan untuk menggunakan handler jika tidak ada lubang interpolasi yang berisi ekspresi await, karena kita dapat secara statis menentukan bahwa jenis handler digunakan dengan aman tanpa analisis rumit tambahan karena handler akan dihilangkan setelah ekspresi string terinterpolasi dievaluasi.
Buka Pertanyaan:
Apakah kita ingin memastikan pengkompilasi mengenali DefaultInterpolatedStringHandler dan melewati panggilan string.Format sepenuhnya? Ini akan memungkinkan kita untuk menyembunyikan metode yang belum tentu ingin kita masukkan ke dalam wajah orang-orang ketika mereka secara manual memanggil string.Format.
Jawab: Ya.
Buka Pertanyaan:
Apakah kita ingin memiliki handler untuk System.IFormattable dan System.FormattableString juga?
Jawaban: Tidak.
Pola penanganan codegen
Di bagian ini, resolusi pemanggilan metode mengacu pada langkah-langkah yang tercantum dalam §12.8.10.2.
Resolusi konstruktor
Mengingat applicable_interpolated_string_handler_typeT dan interpolated_string_expressioni, resolusi pemanggilan metode dan validasi untuk konstruktor yang valid pada T dilakukan sebagai berikut:
- Pencarian anggota untuk konstruktor objek dilakukan pada
T. Grup metode yang dihasilkan disebutM. - Daftar argumen
Adibangun sebagai berikut:- Dua argumen pertama adalah konstanta bilangan bulat, mewakili panjang harfiah
i, dan jumlah interpolasi komponen dalami, masing-masing. - Jika
idigunakan sebagai argumen untuk beberapa parameterpidalam metodeM1, dan parameterpidikaitkan denganSystem.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute, maka untuk setiap namaArgxdalam arrayArgumentsatribut tersebut, pengkompilasi cocok dengan parameterpxyang memiliki nama yang sama. String kosong dicocokkan dengan penerimaM1.- Jika ada
Argxyang tidak dapat dicocokkan dengan parameterM1, atauArgxmeminta penerimaM1danM1adalah metode statis, kesalahan dihasilkan dan tidak ada langkah lebih lanjut yang diambil. - Jika tidak, jenis dari setiap
pxyang telah diselesaikan ditambahkan ke daftar argumen, sesuai urutan yang ditentukan oleh arrayArguments. Setiappxditeruskan dengan semantikrefyang sama seperti yang ditentukan dalamM1.
- Jika ada
- Argumen akhir adalah
bool, diteruskan sebagai parameterout.
- Dua argumen pertama adalah konstanta bilangan bulat, mewakili panjang harfiah
- Resolusi pemanggilan metode tradisional dilakukan dengan grup metode
Mdan daftar argumenA. Untuk tujuan validasi akhir pemanggilan metode, konteksMdiperlakukan sebagai member_access melalui jenisT.- Jika konstruktor terbaik tunggal
Fditemukan, hasil dari resolusi overload adalahF. - Jika tidak ada konstruktor yang berlaku yang ditemukan, langkah 3 dicoba kembali, menghapus parameter
boolakhir dariA. Jika percobaan ulang ini juga tidak menemukan anggota yang berlaku, kesalahan akan dihasilkan dan tidak ada langkah lebih lanjut yang diambil. - Jika tidak ada metode terbaik tunggal yang ditemukan, hasil resolusi kelebihan beban bersifat ambigu, kesalahan dihasilkan, dan tidak ada langkah lebih lanjut yang diambil.
- Jika konstruktor terbaik tunggal
- Validasi akhir pada
Fdilakukan.- Jika ada elemen
Aterjadi secara leksikal setelahi, kesalahan dihasilkan dan tidak ada langkah lebih lanjut yang diambil. - Jika ada
Ameminta penerimaF, danFadalah pengindeks yang digunakan sebagai initializer_target dalam member_initializer, maka kesalahan dilaporkan dan tidak ada langkah lebih lanjut yang diambil.
- Jika ada elemen
Catatan: resolusi di sini sengaja dilakukan tidak menggunakan ekspresi aktual yang diteruskan sebagai argumen lain untuk elemen Argx. Kami hanya mempertimbangkan jenis pasca-konversi. Ini memastikan bahwa kami tidak memiliki masalah konversi ganda, atau kasus tak terduga di mana lambda terikat ke satu jenis delegasi ketika diteruskan ke M1 dan terikat ke jenis delegasi yang berbeda ketika diteruskan ke M.
Catatan: Kami melaporkan kesalahan untuk pengindeks yang digunakan sebagai inisialisasi anggota karena urutan evaluasi untuk penginisialisasi anggota berlapis. Pertimbangkan cuplikan kode ini:
var x1 = new C1 { C2 = { [GetString()] = { A = 2, B = 4 } } };
/* Lowering:
__c1 = new C1();
string argTemp = GetString();
__c1.C2[argTemp][1] = 2;
__c1.C2[argTemp][3] = 4;
Prints:
GetString
get_C2
get_C2
*/
string GetString()
{
Console.WriteLine("GetString");
return "";
}
class C1
{
private C2 c2 = new C2();
public C2 C2 { get { Console.WriteLine("get_C2"); return c2; } set { } }
}
class C2
{
public C3 this[string s]
{
get => new C3();
set { }
}
}
class C3
{
public int A
{
get => 0;
set { }
}
public int B
{
get => 0;
set { }
}
}
Argumen untuk __c1.C2[] dievaluasi sebelum penerima pengindeks. Meskipun kita dapat membuat penurunan yang berfungsi untuk skenario ini (baik dengan membuat sementara untuk __c1.C2 dan membagikannya di kedua pemanggilan pengindeks, atau hanya menggunakannya untuk pemanggilan pengindeks pertama dan berbagi argumen di kedua pemanggilan) kami berpikir bahwa setiap penurunan akan membingungkan untuk apa yang kami yakini adalah skenario patologis. Oleh karena itu, kami melarang skenario sepenuhnya.
Pertanyaan Terbuka :
Jika kita menggunakan konstruktor alih-alih Create, kita akan meningkatkan codegen runtime, dengan sedikit mengorbankan pola.
Jawaban: Kita akan membatasi pada konstruktor untuk saat ini. Kita dapat mempertimbangkan kembali untuk menambahkan metode umum Create nanti, jika skenario muncul.
resolusi kelebihan beban metode Append...
Mengingat applicable_interpolated_string_handler_typeT dan interpolated_string_expressioni, resolusi kelebihan beban untuk serangkaian metode Append... yang valid pada T dilakukan sebagai berikut:
- Jika ada komponen interpolated_regular_string_character dalam
i:- Pencarian anggota di
Tdengan namaAppendLiteraldilakukan. Grup metode yang dihasilkan disebutMl. - Daftar argumen
Aldibangun dengan satu parameter nilai jenisstring. - Resolusi pemanggilan metode tradisional dilakukan dengan grup metode
Mldan daftar argumenAl. Untuk tujuan validasi akhir pemanggilan metode, konteksMldiperlakukan sebagai member_access melalui instansT.- Jika satu-satunya metode terbaik
Fiditemukan dan tidak ada kesalahan yang terjadi, hasil resolusi pemanggilan metode adalahFi. - Jika tidak, kesalahan akan dilaporkan.
- Jika satu-satunya metode terbaik
- Pencarian anggota di
- Untuk setiap komponen interpolasi
ixdarii:- Pencarian anggota di
Tdengan namaAppendFormatteddilakukan. Grup metode yang dihasilkan disebutMf. - Daftar argumen
Afdibangun:- Parameter pertama adalah
expressiondariix, diteruskan dengan nilai. - Jika
ixsecara langsung berisi komponen constant_expression, parameter nilai bilangan bulat ditambahkan, dengan namaalignmentditentukan. - Jika
ixdiikuti langsung oleh interpolation_format, parameter nilai string ditambahkan, dengan namaformatditentukan.
- Parameter pertama adalah
- Resolusi pemanggilan metode tradisional dilakukan dengan grup metode
Mfdan daftar argumenAf. Untuk tujuan validasi akhir pemanggilan metode, konteksMfdiperlakukan sebagai member_access melalui instansT.- Jika metode tunggal terbaik
Fiditemukan, maka hasil dari resolusi pemanggilan metode adalahFi. - Jika tidak, kesalahan akan dilaporkan.
- Jika metode tunggal terbaik
- Pencarian anggota di
- Terakhir, untuk setiap
Fiyang ditemukan di langkah 1 dan 2, validasi akhir dilakukan:- Jika ada
Fiyang tidak mengembalikanboolberdasarkan nilai atauvoid, kesalahan akan dilaporkan. - Jika semua
Fitidak mengembalikan jenis yang sama, kesalahan akan dilaporkan.
- Jika ada
Perhatikan bahwa aturan ini tidak mengizinkan metode ekstensi untuk panggilan Append.... Kita dapat mempertimbangkan untuk mengaktifkan bahwa jika kita memilih, tetapi ini dianalogikan dengan pola enumerator, di mana kita memungkinkan GetEnumerator menjadi metode ekstensi, tetapi tidak Current atau MoveNext().
Aturan ini mengizinkan parameter default untuk panggilan Append..., yang akan berfungsi dengan hal-hal seperti CallerLineNumber atau CallerArgumentExpression (saat didukung oleh bahasa).
Kami memiliki aturan pencarian kelebihan beban terpisah untuk elemen dasar vs lubang interpolasi karena beberapa handler ingin dapat memahami perbedaan antara komponen yang diinterpolasi dan komponen yang merupakan bagian dari string dasar.
Pertanyaan Terbuka
Beberapa skenario, seperti pengelogan terstruktur, ingin dapat memberikan nama untuk elemen interpolasi. Misalnya, hari ini panggilan pengelogan mungkin terlihat seperti Log("{name} bought {itemCount} items", name, items.Count);. Nama-nama di dalam {} memberikan informasi struktur penting untuk pencatat yang membantu memastikan output konsisten dan seragam. Beberapa kasus mungkin dapat menggunakan kembali komponen :format dari lubang interpolasi untuk ini, tetapi banyak pencatat sudah memahami penentu format dan memiliki perilaku yang ada untuk pemformatan output berdasarkan info ini. Apakah ada beberapa sintaksis yang dapat kita gunakan untuk memungkinkan penempatan penentu bernama ini?
Beberapa kasus mungkin dapat lolos dengan CallerArgumentExpression, asalkan dukungan tersedia di C# 10. Namun, untuk kasus yang melibatkan pemanggilan metode/properti, hal itu belum tentu cukup.
Jawaban:
Meskipun ada beberapa bagian menarik untuk string templat yang dapat kita jelajahi dalam fitur bahasa ortogonal, kami tidak berpikir sintaks tertentu di sini memiliki banyak manfaat atas solusi seperti menggunakan tuple: $"{("StructuredCategory", myExpression)}".
Melakukan konversi
Mengingat applicable_interpolated_string_handler_typeT dan interpolated_string_expressioni yang memiliki metode Fc konstruktor dan Append... yang valid Fa diselesaikan, penurunan untuk i dilakukan sebagai berikut:
- Argumen apa pun untuk
Fcyang terjadi secara leksikal sebelumidievaluasi dan disimpan ke dalam variabel sementara dalam urutan leksikal. Untuk mempertahankan pengurutan leksikal, jikaiterjadi sebagai bagian dari ekspresi yang lebih besare, komponeneapa pun yang terjadi sebelumijuga akan dievaluasi, sekali lagi dalam urutan leksikal. -
Fcdipanggil dengan panjang komponen literal string terinterpolasi, jumlah lubang interpolasi , argumen yang dievaluasi sebelumnya, dan argumen keluarbool(jikaFcdiselesaikan dengan salah satu sebagai parameter terakhir). Hasilnya disimpan ke dalam nilai sementaraib.- Panjang komponen harfiah dihitung setelah mengganti open_brace_escape_sequence dengan satu
{, dan mengganti close_brace_escape_sequence dengan satu}.
- Panjang komponen harfiah dihitung setelah mengganti open_brace_escape_sequence dengan satu
- Jika
Fcdiakhiri dengan argumen keluaranbool, sebuah pemeriksaan untuk nilaibooltersebut dihasilkan. Jika benar, metode dalamFaakan dipanggil. Jika tidak, mereka tidak akan dipanggil. - Untuk setiap
FaxdiFa,Faxdipanggil diibdengan komponen literal saat ini atau ekspresi interpolasi , sesuai keperluannya. JikaFaxmengembalikanbool, hasilnya dilakukan operasi AND secara logis dengan semua panggilanFaxsebelumnya.- Jika
Faxadalah panggilan keAppendLiteral, komponen harfiah tidak dilewati dengan mengganti open_brace_escape_sequence dengan satu{, dan close_brace_escape_sequence apa pun dengan satu}.
- Jika
- Hasil konversi adalah
ib.
Sekali lagi, perhatikan bahwa argumen yang diteruskan ke Fc dan argumen yang diteruskan ke e adalah temp yang sama. Konversi dapat terjadi di atas temp untuk mengonversi ke formulir yang Fc butuhkan, tetapi misalnya lambda tidak dapat terikat ke jenis delegasi yang berbeda antara Fc dan e.
Pertanyaan Terbuka
Penurunan ini berarti bahwa bagian berikutnya dari string terinterpolasi setelah panggilan Append... yang mengembalikan nilai salah tidak dievaluasi. Ini berpotensi sangat membingungkan, terutama jika masalah format tersebut menyebabkan efek samping. Kita dapat mengevaluasi semua celah format terlebih dahulu, lalu berulang kali memanggil Append... dengan hasilnya, dan menghentikan jika hasilnya adalah false. Ini akan memastikan bahwa semua ekspresi dievaluasi seperti yang diharapkan, tetapi kita memanggil metode sesedikit yang kita butuhkan. Meskipun evaluasi parsial mungkin diinginkan untuk beberapa kasus yang lebih maju, mungkin tidak intuitif untuk kasus umum.
Alternatif lain, jika kita ingin selalu mengevaluasi semua lubang format, adalah menghapus versi API Append... dan hanya melakukan panggilan Format berulang. Handler dapat melacak apakah hanya perlu mengabaikan argumen dan segera kembali ke versi ini.
Jawaban: Kami akan melakukan evaluasi bersyarat dari lubang.
Pertanyaan Terbuka
Apakah kita perlu membuang jenis handler sekali pakai, dan membungkus panggilan dengan try/finally untuk memastikan bahwa Dispose dipanggil? Misalnya, handler string terinterpolasi di bcl mungkin memiliki array yang disewa di dalamnya, dan jika salah satu celah interpolasi menyebabkan pengecualian selama evaluasi, array yang disewa dapat mengalami kebocoran jika tidak dibuang.
Jawaban: Tidak. handler dapat ditetapkan ke lokal (seperti MyHandler handler = $"{MyCode()};), dan masa pakai handler tersebut tidak jelas. Tidak seperti enumerator foreach, di mana masa pakainya jelas dan tidak ada variabel lokal yang didefinisikan pengguna dibuat untuk enumerator.
Dampak pada tipe referensi yang dapat bernilai null
Untuk meminimalkan kompleksitas implementasi, kami memiliki beberapa batasan tentang bagaimana kami melakukan analisis nullable pada konstruktor handler string terinterpolasi yang digunakan sebagai argumen untuk metode atau pengindeks. Secara khusus, kami tidak menyampaikan informasi dari konstruktor kembali ke tempat asal parameter atau argumen dari konteks asal, dan kami tidak menggunakan tipe parameter konstruktor untuk menginformasikan inferensi tipe generik untuk parameter tipe dalam metode yang memuatnya. Contoh di mana hal ini dapat berdampak adalah:
string s = "";
C c = new C();
c.M(s, $"", c.ToString(), s.ToString()); // No warnings on c.ToString() or s.ToString(), as the `MaybeNull` does not flow back.
public class C
{
public void M(string s1, [InterpolatedStringHandlerArgument("", "s1")] CustomHandler c1, string s2, string s3) { }
}
[InterpolatedStringHandler]
public partial struct CustomHandler
{
public CustomHandler(int literalLength, int formattedCount, [MaybeNull] C c, [MaybeNull] string s) : this()
{
}
}
string? s = null;
M(s, $""); // Infers `string` for `T` because of the `T?` parameter, not `string?`, as flow analysis does not consider the unannotated `T` parameter of the constructor
void M<T>(T? t, [InterpolatedStringHandlerArgument("s1")] CustomHandler<T> c) { }
[InterpolatedStringHandler]
public partial struct CustomHandler<T>
{
public CustomHandler(int literalLength, int formattedCount, T t) : this()
{
}
}
Pertimbangan lain
Izinkan jenis string dapat dikonversi ke handler juga
Untuk kesederhanaan penulis tipe, kita dapat mempertimbangkan untuk memungkinkan ekspresi tipe string dikonversi secara implisit ke applicable_interpolated_string_handler_types. Seperti yang diusulkan saat ini, penulis kemungkinan perlu kelebihan beban pada jenis handler tersebut dan jenis string reguler, sehingga pengguna mereka tidak perlu memahami perbedaannya. Ini mungkin overhead yang mengganggu dan tidak jelas, karena rumusan string tampak sebagai interpolasi dengan panjang expression.Length yang sudah terisi dan 0 lubang yang akan diisi.
Ini akan memungkinkan API baru untuk hanya mengekspos handler, tanpa juga harus mengekspos string-menerima kelebihan beban. Namun, itu tidak akan mengatasi kebutuhan akan perubahan untuk konversi ekspresi yang lebih baik, jadi meskipun itu akan berhasil, itu mungkin menjadi beban yang tidak diperlukan.
Jawaban:
Kami berpikir bahwa ini akhirnya bisa membingungkan, dan ada solusi yang mudah untuk jenis handler kustom: menambahkan konversi yang ditentukan pengguna dari string.
Menggabungkan rentang untuk string tanpa heap
ValueStringBuilder seperti saat ini memiliki 2 konstruktor: satu yang menerima jumlah dan mengalokasikan pada heap secara langsung, dan satu lagi yang menerima Span<char>.
Span<char> itu biasanya berukuran tetap dalam basis kode runtime, rata-rata sekitar 250 elemen. Untuk benar-benar mengganti tipe tersebut, kita harus mempertimbangkan perluasan dari ini di mana kita juga mengenali metode GetInterpolatedString yang mengambil Span<char>, bukan hanya versi penghitung. Namun, kami melihat beberapa kasus duri potensial untuk diselesaikan di sini:
- Kami tidak ingin stackalloc berulang kali dalam loop yang intensif. Jika kita melakukan ekstensi ini ke fitur , kita mungkin ingin berbagi rentang stackalloc'd antara iterasi perulangan. Kita tahu ini aman, karena
Span<T>adalah struct referensi yang tidak dapat disimpan di heap, dan pengguna harus cukup licik untuk berhasil mengekstrak referensi keSpanitu (misalnya dengan membuat metode yang menerima handler semacam itu, kemudian dengan sengaja mengambilSpandari handler dan mengembalikannya ke pemanggil). Namun, mengalokasikan di muka menghasilkan pertanyaan lain:- Haruskah kita antusias dengan penggunaan stackalloc? Bagaimana jika perulangan tidak pernah dijalankan, atau keluar sebelum perlu menggunakan ruang tersebut?
- Jika kita tidak bersemangat stackalloc, apakah itu berarti kita memperkenalkan cabang tersembunyi pada setiap perulangan? Sebagian besar perulangan kemungkinan tidak akan peduli tentang hal ini, tetapi dapat memengaruhi beberapa perulangan ketat yang tidak ingin membayar biaya.
- Beberapa string bisa sangat besar, dan jumlah yang sesuai untuk
stackalloctergantung pada sejumlah faktor, termasuk faktor runtime. Kami tidak benar-benar ingin pengkompilasi dan spesifikasi C# harus menentukan ini sebelumnya, jadi kami ingin menyelesaikan https://github.com/dotnet/runtime/issues/25423 dan menambahkan API untuk panggilan pengkompilasi dalam kasus ini. Ini juga menambahkan lebih banyak pro dan kontra ke poin-poin dari perulangan sebelumnya, di mana kita tidak ingin berpotensi mengalokasikan array besar pada heap berulang kali atau sebelum diperlukan.
Jawaban:
Ini di luar cakupan untuk C# 10. Kita dapat melihat ini secara keseluruhan ketika kita melihat fitur params Span<T> yang lebih umum.
Versi API yang tidak dicoba
Untuk kesederhanaan, spesifikasi ini saat ini hanya mengusulkan pengenalan metode Append..., dan hal-hal yang selalu berhasil (seperti InterpolatedStringHandler) akan selalu mengembalikan nilai benar dari metode tersebut.
Ini dilakukan untuk mendukung skenario pemformatan parsial di mana pengguna ingin berhenti memformat jika terjadi kesalahan atau jika tidak perlu, seperti kasus pengelogan, tetapi berpotensi memperkenalkan banyak cabang yang tidak perlu dalam penggunaan string terinterpolasi standar. Kita dapat mempertimbangkan penambahan di mana kita hanya akan menggunakan metode FormatX jika metode Append... tidak ada, tetapi ini menimbulkan pertanyaan tentang apa yang harus kita lakukan jika ada campuran panggilan Append... dan FormatX.
Jawaban:
Kami menginginkan versi API yang tidak dicoba. Proposal telah diperbarui untuk mencerminkan hal ini.
Meneruskan argumen sebelumnya ke handler
Kurangnya simetri yang menyedihkan dalam proposal yang ada saat ini: memanggil metode ekstensi dalam bentuk yang dipersingkat menghasilkan semantik yang berbeda daripada memanggil metode ekstensi dalam bentuk normal. Ini berbeda dari sebagian besar lokasi lain dalam bahasa, di mana bentuk yang dikurangi hanyalah gula. Kami mengusulkan penambahan atribut ke kerangka kerja yang akan kami kenali saat mengikat metode, yang menginformasikan pengkompilasi bahwa parameter tertentu harus diteruskan ke konstruktor pada handler. Penggunaan terlihat seperti ini:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
public sealed class InterpolatedStringHandlerArgumentAttribute : Attribute
{
public InterpolatedStringHandlerArgumentAttribute(string argument);
public InterpolatedStringHandlerArgumentAttribute(params string[] arguments);
public string[] Arguments { get; }
}
}
Penggunaan ini adalah sebagai berikut:
namespace System
{
public sealed class String
{
public static string Format(IFormatProvider? provider, [InterpolatedStringHandlerArgument("provider")] ref DefaultInterpolatedStringHandler handler);
…
}
}
namespace System.Runtime.CompilerServices
{
public ref struct DefaultInterpolatedStringHandler
{
public DefaultInterpolatedStringHandler(int baseLength, int holeCount, IFormatProvider? provider); // additional factory
…
}
}
var formatted = string.Format(CultureInfo.InvariantCulture, $"{X} = {Y}");
// Is lowered to
var tmp1 = CultureInfo.InvariantCulture;
var handler = new DefaultInterpolatedStringHandler(3, 2, tmp1);
handler.AppendFormatted(X);
handler.AppendLiteral(" = ");
handler.AppendFormatted(Y);
var formatted = string.Format(tmp1, handler);
Pertanyaan yang perlu kami jawab:
- Apakah kita menyukai pola ini secara umum?
- Apakah kita ingin mengizinkan argumen ini berasal setelah parameter handler? Beberapa pola yang ada di BCL, seperti
Utf8Formatter, meletakkan nilai yang akan diformat sebelum benda atau entitas yang perlu diformat. Agar sesuai dengan pola-pola ini, kami mungkin ingin mengizinkan ini, tetapi kami perlu memutuskan apakah evaluasi yang tidak berurutan ini baik-baik saja.
Jawaban:
Kami ingin mendukung ini. Spesifikasi telah diperbarui untuk mencerminkan hal ini. Argumen harus ditentukan dalam urutan leksikal di lokasi pemanggilan, dan jika argumen yang diperlukan untuk metode pembuatan ditentukan setelah string literal yang diinterpolasi, akan terjadi kesalahan.
Penggunaan await dalam lubang interpolasi
Karena $"{await A()}" adalah ekspresi yang valid hari ini, kita perlu merasionalisasi lubang interpolasi dengan menunggu. Kita bisa menyelesaikan ini dengan beberapa aturan:
- Jika string terinterpolasi yang digunakan sebagai
string,IFormattable, atauFormattableStringmemilikiawaitdi lubang interpolasi, kembali ke formatter gaya lama. - Jika string terinterpolasi tunduk pada implicit_string_handler_conversion dan applicable_interpolated_string_handler_type adalah
ref struct,awaittidak diizinkan untuk digunakan dalam lubang format.
Pada dasarnya, desugaring ini dapat menggunakan struktur ref dalam metode asinkron selama kami menjamin bahwa ref struct tidak perlu disimpan ke tumpukan, yang seharusnya dimungkinkan jika kita melarang awaitdalam slot interpolasi.
Atau, kita hanya dapat membuat semua jenis pengelola menjadi struktur non-referensi, termasuk pengelola kerangka kerja untuk string terinterpolasi. Namun, ini akan mencegah kita untuk suatu hari mengenali versi Span yang tidak perlu mengalokasikan ruang sementara sama sekali.
Jawaban:
Kami akan memperlakukan handler string terinterpolasi sama dengan jenis lainnya: ini berarti bahwa jika jenis handler adalah struct ref dan konteks saat ini tidak memungkinkan penggunaan struct ref, itu ilegal untuk menggunakan handler di sini. Spesifikasi seputar penurunan tingkat penggunaan literal string yang digunakan dalam bentuk string sengaja dibuat samar untuk memungkinkan kompilator memutuskan aturan apa yang dianggap sesuai, tetapi untuk jenis handler kustom, mereka harus mengikuti aturan yang sama dengan bagian lain dari bahasa.
Handler sebagai parameter ref
Beberapa handler mungkin ingin diteruskan sebagai parameter ref (baik in atau ref). Haruskah kita izinkan juga? Dan jika demikian, seperti apa akan terlihat handler ref?
ref $"" membingungkan, karena Anda tidak benar-benar meneruskan string dengan ref, tetapi meneruskan handler yang dibuat dari ref melalui ref, dan ini memiliki potensi masalah serupa dengan metode asinkron.
Jawaban:
Kami ingin mendukung ini. Spesifikasi telah diperbarui untuk mencerminkan hal ini. Aturan harus mencerminkan aturan yang sama yang berlaku untuk metode ekstensi pada jenis nilai.
String terinterpolasi melalui ekspresi dan konversi biner
Karena proposal ini membuat konteks string terinterpolasi sensitif, kami ingin mengizinkan pengkompilasi untuk memperlakukan ekspresi biner yang sepenuhnya terdiri dari string terinterpolasi, atau string terinterpolasi yang tunduk pada cast, sebagai string terinterpolasi literal untuk tujuan resolusi kelebihan beban. Misalnya, ikuti skenario berikut:
struct Handler1
{
public Handler1(int literalLength, int formattedCount, C c) => ...;
// AppendX... methods as necessary
}
struct Handler2
{
public Handler2(int literalLength, int formattedCount, C c) => ...;
// AppendX... methods as necessary
}
class C
{
void M(Handler1 handler) => ...;
void M(Handler2 handler) => ...;
}
c.M($"{X}"); // Ambiguous between the M overloads
Ini akan ambigu, mengharuskan konversi tipe ke Handler1 atau Handler2 untuk menyelesaikannya. Namun, dalam pengubahan tipe tersebut, kita berpotensi membuang informasi bahwa terdapat konteks dari objek metode itu sendiri, yang berarti bahwa pengubahan tipe akan gagal karena tidak ada data yang dapat mengisi informasi pada c. Masalah serupa muncul dengan perangkaian string biner: pengguna mungkin ingin memformat literal string di beberapa baris untuk menghindari pemenggalan garis, tetapi tidak bisa melakukannya karena itu tidak lagi menjadi string literal terinterpolasi yang dapat dikonversi menjadi jenis handler.
Untuk mengatasi kasus ini, kami membuat perubahan berikut:
-
additive_expression yang sepenuhnya terdiri dari interpolated_string_expressions dan hanya menggunakan operator
+dianggap sebagai interpolated_string_literal untuk tujuan konversi dan penyelesaian overload. String terinterpolasi akhir dibuat dengan secara logis menggabungkan semua komponen interpolated_string_expression individu, dari kiri ke kanan. -
cast_expression atau relational_expression dengan operator
asyang operannya adalah interpolated_string_expressions dianggap sebagai interpolated_string_expressions untuk keperluan konversi dan penyelesaian kelebihan beban.
Pertanyaan Terbuka:
Apakah kita ingin melakukan ini? Kami tidak melakukan ini untuk System.FormattableString, misalnya, tetapi itu dapat dipecah ke baris yang berbeda, sedangkan ini dapat bergantung pada konteks dan oleh karena itu tidak dapat dipecah menjadi baris yang berbeda. Juga tidak ada masalah resolusi kelebihan beban dengan FormattableString dan IFormattable.
Jawaban:
Kami berpikir bahwa ini adalah kasus penggunaan yang valid untuk ekspresi tambahan, tetapi versi konversi kurang menarik saat ini. Kita dapat menambahkannya nanti jika perlu. Spesifikasi telah diperbarui untuk mencerminkan keputusan ini.
Kasus penggunaan lainnya
Lihat https://github.com/dotnet/runtime/issues/50635 untuk contoh API handler yang diusulkan menggunakan pola ini.
C# feature specifications