Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Not
Bu makale bir özellik belirtimidir. Belirtim, özelliğin tasarım belgesi olarak görev alır. Önerilen belirtim değişikliklerini ve özelliğin tasarımı ve geliştirilmesi sırasında gereken bilgileri içerir. Bu makaleler, önerilen belirtim değişiklikleri son haline getirilene ve geçerli ECMA belirtimine dahil edilene kadar yayımlanır.
Özellik belirtimi ile tamamlanan uygulama arasında bazı tutarsızlıklar olabilir. Bu farklılıklar, ilgili dil tasarım toplantısı (LDM) notlarındakaydedilir.
Özellik belirtimlerini C# dil standardına benimseme işlemi hakkında daha fazla bilgi edinmek için
Şampiyon sorunu: https://github.com/dotnet/csharplang/issues/6051
Özet
C# 10'da sunulan lambda iyileştirmelerinin üzerine oluşturmak için (bkz. ilgili arka plan), lambda'larda varsayılan parametre değerleri ve params dizileri için destek eklemenizi öneririz. Bu, kullanıcıların aşağıdaki lambdaları uygulamasına olanak tanır:
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
Benzer şekilde, yöntem grupları için de aynı davranış türüne izin veririz:
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;
}
İlgili arka plan
C# 10'da Lambda geliştirmeleri
Yöntem grubu dönüştürme belirtimi §10.8
Motivasyon
.NET ekosistemindeki uygulama çerçeveleri, kullanıcıların bir uç noktayla ilişkili iş mantığını hızla yazmasına olanak sağlamak için lambdalardan büyük ölçüde yararlanıyor.
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);
});
Lambda'lar şu anda parametrelerde varsayılan değerleri ayarlamayı desteklemediğinden, bir geliştirici kullanıcıların veri sağlamadığı senaryolara dayanıklı bir uygulama oluşturmak isterse, daha kısa önerilen söz diziminin aksine yerel işlevleri kullanmaya veya lambda gövdesinde varsayılan değerleri ayarlamaya bırakılır.
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);
});
Önerilen söz dizimi ayrıca lambdalar ve yerel işlevler arasındaki kafa karıştırıcı farkları azaltma avantajına da sahiptir. Bu da özellikle yöntem gruplarının başvuru olarak sağlandığı API'lerde lambdaların kullanıldığı diğer senaryolarda, özelliklerden ödün vermeden yapılarla lambdaları işlevlere "büyütmeyi" kolaylaştırır.
Bu aynı zamanda yukarıda belirtilen kullanım örneği senaryosunun kapsamına alınmayan params dizisini desteklemeye yönelik temel motivasyondur.
Mesela:
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);
Önceki davranış
C# 12'nin öncesinde, bir kullanıcı isteğe bağlı veya params parametresine sahip bir lambda uyguladığında, derleyici bir hata oluşturur.
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
Kullanıcı, temel alınan yöntemin isteğe bağlı veya params parametresinin bulunduğu bir yöntem grubu kullanmayı denediğinde, bu bilgiler yayılmaz, bu nedenle yönteme yapılan çağrı beklenen bağımsız değişken sayısındaki uyuşmazlık nedeniyle tür denetimi yapmaz.
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[]>'
Yeni davranış
Bu teklifin (C# 12'nin bir parçası) ardından lambda parametrelerine aşağıdaki davranışla varsayılan değerler ve params uygulanabilir:
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
Varsayılan değerler ve params, özellikle bu tür yöntem grubu tanımlanarak yöntem grubu parametrelerine uygulanabilir:
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
Uyum bozan değişiklik
C# 12'den önce, yöntem grubunun çıkarım türü Action veya Func olduğundan, aşağıdaki kod derlenir:
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 });
}
Bu değişiklikten sonra (C# 12'nin bir parçası), bu tür kodlar .NET SDK 7.0.200 veya sonraki sürümlerde derlenmeyecek.
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 });
}
Bu önemli değişikliğin etkisi göz önünde bulundurulmalıdır. Neyse ki, bir yöntem grubunun türünü çıkarsamak için var kullanımı yalnızca C# 10'dan bu yana desteklenmiştir, bu nedenle yalnızca o zamandan beri yazılmış olan ve bu davranışı açıkça kullanan kod bozulacak.
Ayrıntılı tasarım
Dil bilgisi ve ayrıştırıcı değişiklikleri
Bu geliştirme, lambda ifadeleri için dil bilgisi için aşağıdaki değişiklikleri gerektirir.
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?
;
Bu, varsayılan parametre değerlerine ve params dizilerine yalnızca lambdalar için izin verir, delegate { } söz dizimi ile bildirilen anonim yöntemler için izin vermez.
Yöntem parametreleriyle aynı kurallar (§15.6.2) lambda parametreleri için geçerlidir:
-
ref,outveyathisdeğiştiricisi olan bir parametrenin default_argumentolamaz. - İsteğe bağlı bir parametreden sonra bir parameter_array oluşabilir, ancak varsayılan bir değere sahip olamaz; bunun yerine bir parameter_array için bağımsız değişkenlerin atılması boş bir dizi oluşturulmasına neden olur.
Bu teklif yalnızca anlamlarını değiştireceğinden, yöntem grupları için dil bilgisi değişiklikleri gerekmez.
Anonim işlev dönüştürmeleri (§10.7) için aşağıdaki ekleme (kalın) gereklidir):
Özellikle, anonim bir işlev
Fsağlanan temsilci türüDile uyumludur:
- [...]
açıkça yazılan bir parametre listesi varsa, 'deki her parametre, değiştiricileri ve varsayılan değerleriyoksayarak karşılık gelen parametreyle aynı türe ve değiştiricilere sahiptir.
Önceki tekliflerin güncelleştirmeleri
Önceki teklifin işlev türleri belirtimine (kalın olarak) aşağıdaki ekleme gerekmektedir:
yöntem grubu , yöntem grubundaki tüm aday yöntemler, varsayılan değerler vedeğiştiricilerdahil olmak üzere ortak bir imzaya sahip olduklarında doğal bir türü vardır. (Yöntem grubu uzantı yöntemlerini içerebilirse, adaylar kapsayan türü ve tüm uzantı yöntemi kapsamlarını içerir.)
Anonim işlev ifadesinin veya yöntem grubunun doğal türü function_type. function_type bir yöntem imzasını temsil eder: parametre türleri, varsayılan değerler , başvuru türleri,
paramsdeğiştiricilerve dönüş türü ve başvuru türü. Aynı imzaya sahip anonim işlev ifadeleri veya yöntem grupları aynı function_typesahiptir.
Önceki bir teklifteki temsilci türleri belirtimine aşağıdaki eklemenin (kalın olarak) yapılması gerekmektedir:
Parametre türleri
P1, ..., Pnve dönüş türüRolan anonim işlev veya yöntem grubu için temsilci türü:
- herhangi bir parametre veya dönüş değeri değere göre değilse, veya herhangi bir parametre isteğe bağlı veya
paramsya da 16'dan fazla parametre varsa ya da parametre türlerinden veya dönüşlerinden herhangi biri geçerli tür bağımsız değişkenleri değilse (örneğin,(int* p) => { }), o zaman temsilci, anonim işlev veya yöntem grubuyla eşleşen bir imzaya sahip, sentezlenmişinternalanonim bir temsilci türüdür ve eğer tek bir parametre varsa, parametre adlarıarg1, ..., argnveyaargolur; [...]
Klasör değişiklikleri
Yeni temsilci türlerini sentezleme
ref veya out parametreleri olan temsilcilerin davranışında olduğu gibi, temsilci türleri isteğe bağlı veya params parametrelerle tanımlanan lambdalar veya yöntem grupları için sentezlenir.
Aşağıdaki örneklerde, bu anonim temsilci türlerini temsil etmek için a', b'vb. gösteriminin kullanıldığını unutmayın.
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 = " ");
Dönüştürme ve birleştirme davranışı
İsteğe bağlı parametrelere sahip anonim temsilciler, parametre adından bağımsız olarak aynı parametre (konuma göre) aynı varsayılan değere sahip olduğunda birleştirilir.
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
Son parametre olarak bir diziye sahip olan anonim temsilciler, son parametre aynı params değiştirici ve dizi türüne sahip olduğunda, parametre adından bağımsız olarak birleştirilecektir.
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)`
Benzer şekilde, isteğe bağlı ve params parametrelerini zaten destekleyen adlandırılmış temsilcilerle elbet uyumluluk vardır.
Varsayılan değerler veya params değiştiricileri dönüştürmede farklılık gösterdiğinde, lambda başka bir şekilde çağrılamadığından kaynak değer bir lambda ifadesindeyse kullanılmaz.
Bu, kullanıcılara sezgilere aykırı gelebilir, bu nedenle kaynak varsayılan değer veya params değiştirici mevcut olduğunda ve hedef değerden farklı olduğunda bir uyarı verilir.
Kaynak bir yöntem grubuysa, kendi başına çağrılabilir, bu nedenle uyarı uygulanmaz.
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
IL/çalışma zamanı davranışı
Varsayılan parametre değerleri meta veriye gönderilir. Bu özelliğin IL'i, ref ve out parametrelerine sahip lambdalar için yayılan IL ile doğası gereği çok benzer olacaktır.
System.Delegate veya benzerlerinden devralan bir sınıf oluşturulur ve Invoke yöntemi, isteğe bağlı veya .param parametreleri olan standart adlandırılmış bir temsilci için olduğu gibi varsayılan parametre değerlerini veya System.ParamArrayAttribute ayarlamak için params yönergelerini içerir.
Bu temsilci türleri çalışma zamanında normal şekilde incelenebilir.
Kodda, kullanıcılar ilişkili DefaultValue'yi kullanarak lambda veya yöntem grubuyla ilişkili ParameterInfo'de MethodInfo'ı inceleyebilir.
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
Açık sorular
Bunların hiçbiri uygulanmadı. Açık teklifler olarak kalırlar.
?
Önerilen yanıt: Eşlik için lambdalarda DefaultParameterValue özniteliğine izin verin ve temsilci oluşturma davranışının söz dizimi aracılığıyla desteklenen varsayılan parametre değerleriyle eşleştiğinden emin olun.
var a = (int i = 13) => 1;
// same as
var b = ([DefaultParameterValue(13)] int i) => 1;
b = a; // Allowed
Açık soru: İlk olarak, bunun geçerli teklifin kapsamı dışında olduğunu ancak gelecekte tartışılmaya değer olabileceğini unutmayın. Örtük olarak yazılan lambda parametreleriyle varsayılan değerleri desteklemek istiyor muz? Yani,
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
Bu çıkarım, daha fazla tartışma gerektiren bazı karmaşık dönüştürme sorunlarına yol açar.
Burada ayrıştırma performansıyla ilgili dikkat edilmesi gereken noktalar da vardır. Örneğin, bugün (x = terimi bir lambda ifadesinin başlangıcı olamaz. Lambda varsayılanları için bu söz dizimine izin verildiyse, ayrıştırıcının bir terimin lambda olup olmadığını belirlemek için daha büyük bir lookahead (=> belirteci kadar tarama) gerekir.
Tasarım toplantıları
-
LDM 2022-10-10: varsayılan parametre değerleriyle aynı şekilde
paramsdesteği ekleme kararı.
C# feature specifications