Parameter opsional dan parameter array untuk lambda dan kelompok metode
Catatan
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. Catatan rapat desain bahasa (LDM) yang relevanmencatat perbedaan tersebut.
Anda dapat mempelajari lebih lanjut tentang proses untuk mengadopsi speklet fitur ke dalam standar bahasa C# dalam artikel tentang spesifikasi .
Masalah utama: https://github.com/dotnet/csharplang/issues/6051
Untuk melanjutkan dari peningkatan lambda yang diperkenalkan dalam C# 10 (lihat latar belakang yang relevan), kami mengusulkan penambahan dukungan untuk parameter default dan array params
dalam lambda. Ini akan memungkinkan pengguna untuk mengimplementasikan lambda berikut:
var addWithDefault = (int addTo = 2) => addTo + 1;
addWithDefault(); // 3
addWithDefault(5); // 6
var counter = (params int[] xs) => xs.Length;
counter(); // 0
counter(1, 2, 3); // 3
Demikian pula, kita akan mengizinkan jenis perilaku yang sama untuk grup metode:
var addWithDefault = AddWithDefaultMethod;
addWithDefault(); // 3
addWithDefault(5); // 6
var counter = CountMethod;
counter(); // 0
counter(1, 2); // 2
int AddWithDefaultMethod(int addTo = 2) {
return addTo + 1;
}
int CountMethod(params int[] xs) {
return xs.Length;
}
spesifikasi konversi grup metode §10.8
Kerangka kerja aplikasi dalam ekosistem .NET sangat memanfaatkan lambda untuk memungkinkan pengguna menulis logika bisnis dengan cepat yang terkait dengan titik akhir.
var app = WebApplication.Create(args);
app.MapPost("/todos/{id}", (TodoService todoService, int id, string task) => {
var todo = todoService.Create(id, task);
return Results.Created(todo);
});
Lambdas saat ini tidak mendukung pengaturan nilai default pada parameter, jadi jika pengembang ingin membangun aplikasi yang tahan terhadap skenario di mana pengguna tidak memberikan data, mereka dibiarkan menggunakan fungsi lokal atau mengatur nilai default dalam isi lambda, dibandingkan dengan sintaks yang diusulkan yang lebih succinct.
var app = WebApplication.Create(args);
app.MapPost("/todos/{id}", (TodoService todoService, int id, string task = "foo") => {
var todo = todoService.Create(id, task);
return Results.Created(todo);
});
Sintaks yang diusulkan juga memiliki manfaat mengurangi perbedaan yang membingungkan antara lambda dan fungsi lokal, sehingga lebih mudah untuk memahami konstruksi dan mentransformasi lambda menjadi fungsi tanpa mengorbankan fitur, terutama dalam skenario lain di mana lambda digunakan dalam API yang juga menyediakan kelompok metode sebagai acuan.
Ini juga merupakan motivasi utama untuk mendukung array params
yang tidak dicakup oleh skenario kasus penggunaan yang disebutkan di atas.
Misalnya:
var app = WebApplication.Create(args);
Result TodoHandler(TodoService todoService, int id, string task = "foo") {
var todo = todoService.Create(id, task);
return Results.Created(todo);
}
app.MapPost("/todos/{id}", TodoHandler);
Sebelum C# 12, ketika pengguna mengimplementasikan lambda dengan parameter opsional atau params
, pengkompilasi menimbulkan kesalahan.
var addWithDefault = (int addTo = 2) => addTo + 1; // error CS1065: Default values are not valid in this context.
var counter = (params int[] xs) => xs.Length; // error CS1670: params is not valid in this context
Ketika pengguna mencoba menggunakan kelompok metode di mana metode yang mendasar memiliki parameter opsional atau params
, informasi ini tidak diteruskan, sehingga pemanggilan metode tidak dapat memvalidasi tipe karena ketidakcocokan dalam jumlah argumen yang sesuai.
void M1(int i = 1) { }
var m1 = M1; // Infers Action<int>
m1(); // error CS7036: There is no argument given that corresponds to the required parameter 'obj' of 'Action<int>'
void M2(params int[] xs) { }
var m2 = M2; // Infers Action<int[]>
m2(); // error CS7036: There is no argument given that corresponds to the required parameter 'obj' of 'Action<int[]>'
Mengikuti proposal ini (bagian dari C# 12), nilai default dan params
dapat diterapkan ke parameter lambda dengan perilaku berikut:
var addWithDefault = (int addTo = 2) => addTo + 1;
addWithDefault(); // 3
addWithDefault(5); // 6
var counter = (params int[] xs) => xs.Length;
counter(); // 0
counter(1, 2, 3); // 3
Nilai default dan params
dapat diterapkan ke parameter grup metode dengan secara khusus menentukan grup metode tersebut:
int AddWithDefault(int addTo = 2) {
return addTo + 1;
}
var add1 = AddWithDefault;
add1(); // ok, default parameter value will be used
int Counter(params int[] xs) {
return xs.Length;
}
var counter1 = Counter;
counter1(1, 2, 3); // ok, `params` will be used
Sebelum C# 12, jenis grup metode yang disimpulkan Action
atau Func
sehingga kode berikut mengkompilasi:
void WriteInt(int i = 0) {
Console.Write(i);
}
var writeInt = WriteInt; // Inferred as Action<int>
DoAction(writeInt, 3); // Ok, writeInt is an Action<int>
void DoAction(Action<int> a, int p) {
a(p);
}
int Count(params int[] xs) {
return xs.Length;
}
var counter = Count; // Inferred as Func<int[], int>
DoFunction(counter, 3); // Ok, counter is a Func<int[], int>
int DoFunction(Func<int[], int> f, int p) {
return f(new[] { p });
}
Setelah perubahan ini (bagian dari C# 12), kode sifat ini berhenti dikompilasi di .NET SDK 7.0.200 atau yang lebih baru.
void WriteInt(int i = 0) {
Console.Write(i);
}
var writeInt = WriteInt; // Inferred as anonymous delegate type
DoAction(writeInt, 3); // Error, cannot convert from anonymous delegate type to Action
void DoAction(Action<int> a, int p) {
a(p);
}
int Count(params int[] xs) {
return xs.Length;
}
var counter = Count; // Inferred as anonymous delegate type
DoFunction(counter, 3); // Error, cannot convert from anonymous delegate type to Func
int DoFunction(Func<int[], int> f, int p) {
return f(new[] { p });
}
Dampak dari perubahan besar ini perlu dipertimbangkan. Untungnya, penggunaan var
untuk menyimpulkan jenis grup metode hanya didukung sejak C# 10, jadi hanya kode yang telah ditulis sejak saat itu yang secara eksplisit bergantung pada perilaku ini akan rusak.
Peningkatan ini memerlukan perubahan berikut pada tata bahasa untuk ekspresi lambda.
lambda_expression
: modifier* identifier '=>' (block | expression)
- | attribute_list* modifier* type? lambda_parameters '=>' (block | expression)
+ | attribute_list* modifier* type? lambda_parameter_list '=>' (block | expression)
;
+lambda_parameter_list
+ : lambda_parameters (',' parameter_array)?
+ | parameter_array
+ ;
lambda_parameter
: identifier
- | attribute_list* modifier* type? identifier
+ | attribute_list* modifier* type? identifier default_argument?
;
Perhatikan bahwa ini memungkinkan nilai parameter default dan array params
hanya untuk lambda, bukan untuk metode anonim yang dideklarasikan dengan sintaks delegate { }
.
Aturan yang sama seperti untuk parameter metode (§15.6.2) berlaku untuk parameter lambda:
- Parameter dengan pengubah
ref
,out
, atauthis
tidak dapat memiliki default_argument. - parameter_array dapat terjadi setelah parameter opsional, tetapi tidak dapat memiliki nilai bawaan – penghilangan argumen untuk parameter_array akan mengakibatkan pembuatan array kosong.
Tidak ada perubahan pada tata bahasa yang diperlukan untuk grup metode karena proposal ini hanya akan mengubah semantik mereka.
Penambahan berikut (dalam huruf tebal) diperlukan untuk konversi fungsi anonim (§10,7):
Secara khusus, fungsi anonim
F
kompatibel dengan jenis delegasiD
disediakan:
- [...]
- Jika
F
memiliki daftar parameter yang diketik secara eksplisit, setiap parameter dalamD
memiliki jenis dan pengubah yang sama dengan parameter yang sesuai dalamF
mengabaikan pengubahparams
dan nilai default.
Penambahan berikut (dalam huruf tebal) diperlukan untuk spesifikasi jenis fungsi dalam proposal sebelumnya:
Grup metode memiliki tipe alami jika semua metode kandidat dalam grup tersebut memiliki tanda tangan bersama , termasuk nilai default dan pengubah
params
. (Jika grup metode dapat mencakup metode ekstensi, kandidat menyertakan jenis yang berisi dan semua cakupan metode ekstensi.)
Jenis alami ekspresi fungsi anonim atau grup metode adalah function_type. function_type mewakili spesifikasi metode: jenis parameter, nilai default, jenis referensi, pengubah
params
, serta jenis pengembalian dan jenis referensi. Ekspresi fungsi anonim atau grup metode dengan tanda tangan yang sama memiliki function_typeyang sama.
Penambahan berikut (dalam huruf tebal) diperlukan untuk jenis delegasi spesifikasi dalam proposal sebelumnya:
Jenis delegasi untuk fungsi anonim atau grup metode dengan jenis parameter
P1, ..., Pn
dan jenis pengembalianR
adalah:
- jika ada parameter atau nilai yang dikembalikan bukan berdasarkan nilai, atau parameter apa pun bersifat opsional atau
params
, atau ada lebih dari 16 parameter, atau salah satu jenis parameter atau pengembalian bukan argumen jenis yang valid (misalnya,(int* p) => { }
), maka delegasi adalah jenis delegasi anonim yang disintesisinternal
dengan tanda tangan yang cocok dengan fungsi anonim atau grup metode, dan dengan nama parameterarg1, ..., argn
atauarg
jika satu parameter; [...]
Seperti halnya perilaku untuk delegasi dengan parameter ref
atau out
, tipe delegasi dihasilkan untuk lambda atau grup metode yang ditentukan dengan parameter opsional atau params
.
Perhatikan bahwa dalam contoh di bawah ini, notasi a'
, b'
, dll. digunakan untuk mewakili jenis delegasi anonim ini.
var addWithDefault = (int addTo = 2) => addTo + 1;
// internal delegate int a'(int arg = 2);
var printString = (string toPrint = "defaultString") => Console.WriteLine(toPrint);
// internal delegate void b'(string arg = "defaultString");
var counter = (params int[] xs) => xs.Length;
// internal delegate int c'(params int[] arg);
string PathJoin(string s1, string s2, string sep = "/") { return $"{s1}{sep}{s2}"; }
var joinFunc = PathJoin;
// internal delegate string d'(string arg1, string arg2, string arg3 = " ");
Delegasi anonim dengan parameter opsional akan disatukan ketika parameter yang sama (berdasarkan posisi) memiliki nilai default yang sama, terlepas dari nama parameter.
int E(int j = 13) {
return 11;
}
int F(int k = 0) {
return 3;
}
int G(int x = 13) {
return 4;
}
var a = (int i = 13) => 1;
// internal delegate int b'(int arg = 13);
var b = (int i = 0) => 2;
// internal delegate int c'(int arg = 0);
var c = (int i = 13) => 3;
// internal delegate int b'(int arg = 13);
var d = (int c = 13) => 1;
// internal delegate int b'(int arg = 13);
var e = E;
// internal delegate int b'(int arg = 13);
var f = F;
// internal delegate int c'(int arg = 0);
var g = G;
// internal delegate int b'(int arg = 13);
a = b; // Not allowed
a = c; // Allowed
a = d; // Allowed
c = e; // Allowed
e = f; // Not Allowed
b = f; // Allowed
e = g; // Allowed
d = (int c = 10) => 2; // Warning: default parameter value is different between new lambda
// and synthesized delegate b'. We won't do implicit conversion
Delegasi anonim dengan array sebagai parameter terakhir akan disatukan ketika parameter terakhir memiliki pengubah params
dan jenis array yang sama, terlepas dari nama parameter.
int C(int[] xs) {
return xs.Length;
}
int D(params int[] xs) {
return xs.Length;
}
var a = (int[] xs) => xs.Length;
// internal delegate int a'(int[] xs);
var b = (params int[] xs) => xs.Length;
// internal delegate int b'(params int[] xs);
var c = C;
// internal delegate int a'(int[] xs);
var d = D;
// internal delegate int b'(params int[] xs);
a = b; // Not allowed
a = c; // Allowed
b = c; // Not allowed
b = d; // Allowed
c = (params int[] xs) => xs.Length; // Warning: different delegate types; no implicit conversion
d = (int[] xs) => xs.Length; // OK. `d` is `delegate int (params int[] arg)`
Demikian pula, tentu saja ada kompatibilitas dengan delegasi bernama yang sudah mendukung parameter opsional dan params
.
Ketika nilai default atau pengubah params
berbeda dalam konversi, sumber akan tidak digunakan jika berada dalam ekspresi lambda, karena lambda tidak dapat dipanggil dengan cara lain.
Itu mungkin tampak kontra-intuitif bagi pengguna, oleh karena itu peringatan akan dipancarkan ketika nilai default sumber atau pengubah params
ada dan berbeda dari target.
Jika sumbernya adalah grup metode, sumber dapat dipanggil sendiri, sehingga tidak ada peringatan yang akan dipancarkan.
delegate int DelegateNoDefault(int x);
delegate int DelegateWithDefault(int x = 1);
int MethodNoDefault(int x) => x;
int MethodWithDefault(int x = 2) => x;
DelegateNoDefault d1 = MethodWithDefault; // no warning: source is a method group
DelegateWithDefault d2 = MethodWithDefault; // no warning: source is a method group
DelegateWithDefault d3 = MethodNoDefault; // no warning: source is a method group
DelegateNoDefault d4 = (int x = 1) => x; // warning: source present, target missing
DelegateWithDefault d5 = (int x = 2) => x; // warning: source present, target different
DelegateWithDefault d6 = (int x) => x; // no warning: source missing, target present
delegate int DelegateNoParams(int[] xs);
delegate int DelegateWithParams(params int[] xs);
int MethodNoParams(int[] xs) => xs.Length;
int MethodWithParams(params int[] xs) => xs.Length;
DelegateNoParams d7 = MethodWithParams; // no warning: source is a method group
DelegateWithParams d8 = MethodNoParams; // no warning: source is a method group
DelegateNoParams d9 = (params int[] xs) => xs.Length; // warning: source present, target missing
DelegateWithParams d10 = (int[] xs) => xs.Length; // no warning: source missing, target present
Nilai parameter default akan dipancarkan ke metadata. IL untuk fitur ini akan sangat mirip secara alami dengan IL yang dipancarkan untuk lambda dengan parameter ref
dan out
. Kelas yang mewarisi dari System.Delegate
atau sejenisnya akan dihasilkan, dan metode Invoke
akan menyertakan arahan .param
untuk mengatur nilai parameter default atau System.ParamArrayAttribute
- seperti halnya untuk delegasi standar bernama dengan parameter opsional atau params
.
Jenis delegasi ini dapat diperiksa pada runtime, seperti biasa.
Dalam kode, pengguna dapat mengintrospeksi DefaultValue
pada ParameterInfo
yang dihubungkan dengan lambda atau kelompok metode dengan menggunakan MethodInfo
yang terkait.
var addWithDefault = (int addTo = 2) => addTo + 1;
int AddWithDefaultMethod(int addTo = 2)
{
return addTo + 1;
}
var defaultParm = addWithDefault.Method.GetParameters()[0].DefaultValue; // 2
var add1 = AddWithDefaultMethod;
defaultParm = add1.Method.GetParameters()[0].DefaultValue; // 2
Tidak satu pun dari ini telah diimplementasikan. Proposals tersebut tetap terbuka.
Buka pertanyaan: bagaimana hal ini berinteraksi dengan atribut DefaultParameterValue
yang ada?
Usulan jawaban: Untuk paritas, izinkan atribut DefaultParameterValue
pada lambda dan pastikan bahwa perilaku pembuatan delegasi cocok untuk nilai parameter default yang didukung melalui sintaks.
var a = (int i = 13) => 1;
// same as
var b = ([DefaultParameterValue(13)] int i) => 1;
b = a; // Allowed
Pertanyaan terbuka: Pertama, perhatikan bahwa ini berada di luar cakupan proposal saat ini tetapi mungkin perlu dibahas di masa depan. Apakah kita ingin mendukung default dengan parameter lambda yang ditik secara implisit? Yaitu,
delegate void M1(int i = 3);
M1 m = (x = 3) => x + x; // Ok
delegate void M2(long i = 2);
M2 m = (x = 3.0) => ...; //Error: cannot convert implicitly from long to double
Inferensi ini menyebabkan beberapa masalah konversi yang sulit yang akan membutuhkan lebih banyak diskusi.
Terdapat juga pertimbangan performa penguraian di sini. Misalnya, saat ini istilah (x =
tidak pernah bisa menjadi awal ekspresi lambda. Jika sintaks ini diizinkan untuk default lambda, maka pengurai akan membutuhkan tinjauan ke depan yang lebih besar (memindai hingga token =>
) untuk menentukan apakah elemen tersebut adalah lambda atau bukan.
Umpan balik C# feature specifications
C# feature specifications adalah proyek sumber terbuka. Pilih tautan untuk memberikan umpan balik: