Aracılığıyla paylaş


Lambda geliştirmeleri

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 belirtimleri makalesinde bulabilirsiniz.

Şampiyon sorunu: https://github.com/dotnet/csharplang/issues/4934

Özet

Önerilen değişiklikler:

  1. Öznitelikleri olan lambdalara izin ver
  2. Açık dönüş türüne sahip lambdalara izin ver
  3. Lambda ve metot grupları için doğal bir temsilci türü türet

Motivasyon

Lambda'lardaki öznitelikler için destek, metotlar ve yerel işlevlerle eşitlik sağlar.

Açık dönüş türleri desteği, açık türlerin belirtilebileceği lambda parametreleriyle simetri sağlar. Açık dönüş türlerine izin vermek, aşırı yükleme çözümlemesinin imzayı belirlemek için şu anda lambda gövdesini bağlaması gereken iç içe lambdalarda, derleyici performansı üzerinde daha iyi bir kontrol imkanı da sağlar.

Lambda ifadeleri ve yöntem grupları için doğal bir tür, var bildirimlerinde başlatıcılar olarak dahil olmak üzere açık bir temsilci türü olmadan lambdaların ve yöntem gruplarının kullanılabildiği daha fazla senaryoya olanak sağlar.

Lambdalar ve yöntem grupları için açık temsilci türlerinin zorunlu olması müşteriler için bir sürtüşme noktası olmuştur ve MapActionüzerinde yapılan son çalışmalarla ASP.NET ilerlemesi engel haline gelmiştir.

ASP.NET MapAction önerilen değişiklikler olmadan (MapAction() bir System.Delegate bağımsız değişken alır):

[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction((Func<Todo>)GetTodo);

[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo;
app.MapAction((Func<Todo, Todo>)PostTodo);

ASP.NET MapAction, yöntem grupları için doğal türler ile.

[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction(GetTodo);

[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo;
app.MapAction(PostTodo);

ASP.NET MapAction, lambda ifadeleri için öznitelikler ve doğal türler ile birlikte:

app.MapAction([HttpGet("/")] () => new Todo(Id: 0, Name: "Name"));
app.MapAction([HttpPost("/")] ([FromBody] Todo todo) => todo);

Öznitelik

Lambda ifadelerine ve lambda parametrelerine öznitelikler eklenebilir. Yöntem öznitelikleri ile parametre öznitelikleri arasındaki belirsizliği önlemek için öznitelikleri olan bir lambda ifadesinin parantez içinde parametre listesi kullanması gerekir. Parametre türleri gerekli değildir.

f = [A] () => { };        // [A] lambda
f = [return:A] x => x;    // syntax error at '=>'
f = [return:A] (x) => x;  // [A] lambda
f = [A] static x => x;    // syntax error at '=>'

f = ([A] x) => x;         // [A] x
f = ([A] ref int x) => x; // [A] x

Aynı öznitelik listesinde virgülle ayrılmış veya ayrı öznitelik listeleri olarak birden çok öznitelik belirtilebilir.

var f = [A1, A2][A3] () => { };    // ok
var g = ([A1][A2, A3] int x) => x; // ok

Öznitelikler, söz dizimi ile bildirilen delegate { } yöntemler için desteklenmez.

f = [A] delegate { return 1; };         // syntax error at 'delegate'
f = delegate ([A] int x) { return x; }; // syntax error at '['

Ayrıştırıcı, öğe ataması olan bir koleksiyon başlatıcısını lambda ifadesiyle bir koleksiyon başlatıcısından ayırt etmek için ileriye bakar.

var y = new C { [A] = x };    // ok: y[A] = x
var z = new C { [A] x => x }; // ok: z[0] = [A] x => x

Ayrıştırıcı, ?[ koşullu öğe erişiminin başlangıcı olarak ele alır.

x = b ? [A];               // ok
y = b ? [A] () => { } : z; // syntax error at '('

Lambda ifadesi veya lambda parametrelerindeki öznitelikler, lambda'ya karşılık gelen yöntemdeki meta verilere aktarılır.

Genel olarak, müşteriler lambda ifadelerinin ve yerel işlevlerin kaynaktan meta veriye nasıl eşlenmesine bağlı olmamalıdır. Derleyici sürümleri arasında lambda ve yerel işlevlerin yayılma şekli değişebilir ve değişmiştir.

Burada önerilen değişiklikler Delegate temelli senaryoya hedeflenmiştir. Bir MethodInfo örneğiyle ilişkili olan Delegate'ı incelemek, lambda ifadesinin veya yerel işlevin imzasını belirlemek için, varsa açık öznitelikler ve derleyici tarafından eklenen varsayılan parametreler gibi ek meta veriler de dahil olmak üzere, geçerli olmalıdır. Bu, ASP.NET gibi ekiplerin lambdalar ve yerel işlevler için sıradan yöntemlerle aynı davranışları sunmasını sağlar.

Açık dönüş türü

Parantez içinde parametre listesinden önce açık bir dönüş türü belirtilebilir.

f = T () => default;                    // ok
f = short x => 1;                       // syntax error at '=>'
f = ref int (ref int x) => ref x;       // ok
f = static void (_) => { };             // ok
f = async async (async async) => async; // ok?

Ayrıştırıcı, bir yöntem çağrısını T() bir lambda ifadesinden T () => eayırt etmek için ileri doğru bakar.

delegate { } söz dizimi ile bildirilen anonim yöntemler için açık dönüş türleri desteklenmez.

f = delegate int { return 1; };         // syntax error
f = delegate int (int x) { return x; }; // syntax error

Yöntem türü çıkarımı, açık bir lambda dönüş türünden tam bir çıkarım yapmalıdır.

static void F<T>(Func<T, T> f) { ... }
F(int (i) => i); // Func<int, int>

Lambda dönüş türünden temsilci dönüş türüne (parametre türleri için benzer davranışla eşleşen) varyans dönüştürmelerine izin verilmez.

Func<object> f1 = string () => null; // error
Func<object?> f2 = object () => x;   // warning

Ayrıştırıcı, ifadeler içinde ek parantezler olmadan ref dönüş türlerine sahip lambda ifadelerine izin verir.

d = ref int () => x; // d = (ref int () => x)
F(ref int () => x);  // F((ref int () => x))

var, lambda ifadeleri için açık bir dönüş türü olarak kullanılamaz.

class var { }

d = var (var v) => v;              // error: contextual keyword 'var' cannot be used as explicit lambda return type
d = @var (var v) => v;             // ok
d = ref var (ref var v) => ref v;  // error: contextual keyword 'var' cannot be used as explicit lambda return type
d = ref @var (ref var v) => ref v; // ok

Doğal (işlev) türü

anonim işlev ifadesi (§12.19) ( lambda ifadesi veya anonim yöntem) parametre türleri açıksa ve dönüş türü açıksa veya çıkarılabiliyorsa doğal bir türe sahiptir (bkz. §12.6.3.13).

Yöntem grubundaki tüm aday yöntemlerin ortak bir imzası varsa, yöntem grubu doğal bir türe sahiptir. (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 ve başvuru türleri ve dönüş türü ve başvuru türü. Aynı imzaya sahip anonim işlev ifadeleri veya yöntem grupları aynı function_typesahiptir.

Function_types yalnızca birkaç belirli bağlamda kullanılır:

  • örtük ve açık dönüştürmeler
  • yöntem türü çıkarımı (§12.6.3) ve en iyi ortak tür (§12.6.3.15)
  • var başlatıcıları

Yalnızca derleme zamanında bir function_type vardır: function_types kaynakta veya meta verilerde görünmez.

Dönüşüm

function_typeF biriminden function_type dönüştürme işlemleri örtük olarak vardır.

  • Parametreler ve dönüş türleri 'ün, G'ün parametreleri ve dönüş türüne değişken olarak dönüştürülebilir olduğu durumda, FG olur.
  • System.MulticastDelegate veya System.MulticastDelegate'in temel sınıfları ya da arabirimleri
  • System.Linq.Expressions.Expression veya System.Linq.Expressions.LambdaExpression

Anonim işlev ifadeleri ve yöntem grupları, zaten ifade 'den temsilci türlerine ve ifad ağacı türlerine dönüşümlere sahiptir (bkz. anonim işlev dönüşümleri §10.7 ve yöntem grubu dönüşümleri §10.8). Bu dönüştürmeler, kesin olarak belirlenmiş temsilci türlerine ve ifade ağacı türlerine dönüştürmek için yeterlidir. Yukarıdaki function_type dönüştürmeleri, yalnızca tür 'ten temel türlere dönüştürmeleri ekler: System.MulticastDelegate, System.Linq.Expressions.Expressionvb.

Bir türden function_type'ye, function_typedışında bir dönüştürme yoktur. Kaynakta function_types başvurulamadığından, function_types için açık bir dönüştürme yoktur.

System.MulticastDelegate veya temel türe veya arabirime dönüştürme, anonim işlevi veya yöntem grubunu uygun bir temsilci türünün örneği olarak gerçekleştirir. System.Linq.Expressions.Expression<TDelegate> veya temel türe dönüştürme, lambda ifadesini uygun bir temsilci türüne sahip bir ifade ağacı olarak gerçekleştirir.

Delegate d = delegate (object obj) { }; // Action<object>
Expression e = () => "";                // Expression<Func<string>>
object o = "".Clone;                    // Func<object>

Function_type dönüştürmeleri , §10.4 örtük veya açık standart dönüştürmeler değildir ve kullanıcı tanımlı dönüştürme işlecinin anonim bir işlev veya yöntem grubu için geçerli olup olmadığı belirlenirken dikkate alınmaz. Kullanıcı tanımlı dönüştürmelerin değerlendirilmesinden §10.5.3:

Dönüştürme işlecinin uygulanabilir olması için, kaynak türünden işlecin işlenen türüne standart dönüştürme (§10,4) gerçekleştirilmesi ve işlecin sonuç türünden hedef türüne standart bir dönüştürme gerçekleştirilmesi mümkün olmalıdır.

class C
{
    public static implicit operator C(Delegate d) { ... }
}

C c;
c = () => 1;      // error: cannot convert lambda expression to type 'C'
c = (C)(() => 2); // error: cannot convert lambda expression to type 'C'

Bir metod grubu, object'a örtük olarak dönüştürüldüğü için bir uyarı bildirilir; bu, dönüştürmenin geçerli ancak belki de istem dışı olduğuna işaret eder.

Random r = new Random();
object obj;
obj = r.NextDouble;         // warning: Converting method group to 'object'. Did you intend to invoke the method?
obj = (object)r.NextDouble; // ok

Tür çıkarımı

Tür çıkarımı için mevcut kurallar çoğunlukla değişmemiştir (bkz. §12.6.3). Ancak, aşağıda tür çıkarımının belirli aşamalarında birkaç değişiklik vardır.

İlk aşama

İlk aşama (§12.6.3.2), anonim bir işlevin, Ti bir temsilci veya ifade ağacı türü olmasa bile (örneğin Ti kısıtlanmış bir tür parametresi) System.Delegate'ye bağlanmasına olanak tanır.

yöntem bağımsız değişkenlerinin her biri için Ei:

  • Ei anonim bir işlev ve Tibir temsilci türü veya ifade ağacı türüyse, 'den Ei'a Ti ve 'den Ei'e Ti yapılır.
  • Aksi takdirde, Ei bir tür U varsa ve xi bir değer parametresiyse, 'denU'eTi yapılır.
  • Aksi takdirde, Ei bir tür U'e sahipse ve xiref veya out bir parametreyse, Tiyapılır.
  • Aksi takdirde, bu bağımsız değişkene dair bir çıkarımda bulunulmaz.

Açık dönüş türü çıkarımı

Bir açık dönüş türü çıkarımı, bir tür aşağıdaki şekildeiçin bir ifade ET yapılır:

  • E, Ur açık dönüş türüne sahip anonim bir işlevse ve T dönüş türü Vr olan bir temsilci türü veya ifade ağacı türüyse, tam bir çıkarım (§12.6.3.9) ,Ur'denVr'e yapılır.

Düzeltme

Düzeltme (§12.6.3.12) diğer dönüştürmelerin function_type dönüştürmeler yerine tercih edilmesini sağlar. (Lambda ifadeleri ve yöntem grubu ifadeleri yalnızca alt sınırlara katkıda bulunur, bu nedenle yalnızca alt sınırlar için function_types işlenmesi gerekir.)

Sınır kümesine sahip Xi türü değişkeni aşağıdaki gibi sabit :

  • Ujaday türleri kümesi,işlev türü olmayan herhangi bir tür varsa, işlev türlerinin alt sınırlarda yoksayıldığı Xiiçin sınır kümesindeki tüm türlerin kümesi olarak başlar.
  • Ardından her Xi'ı teker teker inceleyeceğiz: Her U'nin kesin sınırı Xi için, Uj ile aynı olmayan tüm U türleri aday kümesinden kaldırılır. U her alt sınır Xi için, Uj örtük dönüştürme olmayanU tüm türler aday kümesinden kaldırılır. U her üst sınır Xi için, Uj örtük dönüştürme olmayan tüm türler U aday kümesinden kaldırılır.
  • Kalan aday türleri arasında Uj diğer tüm aday türlerine örtük dönüştürmenin olduğu benzersiz bir tür V varsa, XiVolarak sabitlenmiştir.
  • Aksi takdirde tür çıkarımı başarısız olur.

En iyi yaygın tür

En iyi ortak tür (§12.6.3.15) tür çıkarımı açısından tanımlanır, dolayısıyla yukarıdaki tür çıkarımı değişiklikleri en iyi ortak tür için de geçerlidir.

var fs = new[] { (string s) => s.Length, (string s) => int.Parse(s) }; // Func<string, int>[]

var

İşlev türlerine sahip anonim işlevler ve yöntem grupları, var bildirimlerinde başlatıcı olarak kullanılabilir.

var f1 = () => default;           // error: cannot infer type
var f2 = x => x;                  // error: cannot infer type
var f3 = () => 1;                 // System.Func<int>
var f4 = string () => null;       // System.Func<string>
var f5 = delegate (object o) { }; // System.Action<object>

static void F1() { }
static void F1<T>(this T t) { }
static void F2(this string s) { }

var f6 = F1;    // error: multiple methods
var f7 = "".F1; // error: the delegate type could not be inferred
var f8 = F2;    // System.Action<string> 

İşlev türleri, yok saymalarda atamalarda kullanılmaz.

d = () => 0; // ok
_ = () => 1; // error

Temsilci türleri

Parametre türleri P1, ..., Pn ve dönüş türü R olan 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 ya 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) => { }), temsilci anonim işlev veya yöntem grubuyla eşleşen imzalı, parametre adları internal veya tek bir parametreyse arg1, ..., argn olan birleştirilmiş arg anonim temsilci türüdür;
  • R voidise, temsilci türü System.Action<P1, ..., Pn>olur;
  • aksi takdirde temsilci türü System.Func<P1, ..., Pn, R>.

Derleyici, gelecekte System.Action<> ve System.Func<> türlerine daha fazla imza bağlanmasına izin verebilir (örneğin, ref struct türleri bağımsız değişken olarak kabul ediliyorsa).

Yöntem grubu imzasındaki modopt() veya modreq(), karşılık gelen temsilci türünde göz ardı edilir.

Aynı derlemedeki iki anonim işlev veya yöntem grubu aynı parametre türlerine ve değiştiricilere ve aynı dönüş türüne ve değiştiricilere sahip sentezlenmiş temsilci türleri gerektiriyorsa, derleyici aynı sentezlenmiş temsilci türünü kullanır.

Aşırı yükleme çözümü

Daha iyi fonksiyon üyesi (§12.6.4.3), dönüştürmelerin ve lambda ifadelerinden veya yöntem gruplarından çıkarılan tür bağımsız değişkenlerinin hiçbirinin yer almadığı üyeleri tercih edecek şekilde güncellendi.

Daha iyi işlev üyesi

... Bağımsız değişken listesi A, bir dizi bağımsız değişken ifadesi {E1, E2, ..., En} ve parametre türleri Mp ve Mqolan iki geçerli işlev üyesi {P1, P2, ..., Pn} ve {Q1, Q2, ..., Qn} ile verildiğinde, Mp'nın daha iyi bir işlev üyesi olduğu, Mq'e göre tanımlanır.

  1. Her bağımsız değişken için , Ex'den Px'ye örtük bir dönüştürme, bir işlev türü dönüşümüdeğildir ve
    • Mp genel olmayan bir yöntemdir veya Mp tür parametreleri {X1, X2, ..., Xp} ve tür bağımsız değişkeni bir ifadeden veya Xidışında bir türden çıkarılır her tür parametresi için genel bir yöntemdir ve
    • En az bir bağımsız değişken için Yi tür parametreleri ve en az bir tür parametresi için genel bir yöntemdir
  2. her bağımsız değişken için, Ex'den Qx'ye örtük dönüştürme, Ex'den Px'a örtük dönüştürmeden daha iyi değildir ve en az bir bağımsız değişken için Ex'den Px dönüştürme, Ex'den Qx'e dönüştürmeden daha iyidir.

§12.6.4.5numaralı ifade için daha iyi bir dönüştürme, lambda ifadeleri veya yöntem gruplarından çıkarılan türleri içermeyen dönüştürmelerin tercih edilmesi yönünde güncellenmiştir.

İfadedeki dönüşümü iyileştirme

C1 bir ifadeden Etürüne dönüştüren örtük dönüştürme T1 ve bir ifadeden C2 tür Etürüne dönüştüren örtük dönüştürme T2 göz önünde bulundurulduğunda, C1 daha iyi C2:

  1. C1 bir fonksiyon_türü_dönüştürme değil ve C2 bir fonksiyon_türü_dönüştürmeveya
  2. E sabit olmayan bir ara_değişken_dizi_ifadesi'dir, C1 bir örtük_dizi_dönüştürücüsü'dir, T1 bir uygulanabilir_ara_değişken_dizi_tipi'dir ve C2 bir örtük_dizi_dönüştürücüsüdeğildir ya da
  3. E T2 tam olarak eşleşmez ve aşağıdakilerden en az biri geçerlidir:
    • E T1 tam olarak eşleşir (§12.6.4.5)
    • T1, T2'den daha iyi bir dönüştürme hedefidir (§12.6.4.7)

Sözdizimi

lambda_expression
  : modifier* identifier '=>' (block | expression)
  | attribute_list* modifier* type? lambda_parameters '=>' (block | expression)
  ;

lambda_parameters
  : lambda_parameter
  | '(' (lambda_parameter (',' lambda_parameter)*)? ')'
  ;

lambda_parameter
  : identifier
  | attribute_list* modifier* type? identifier equals_value_clause?
  ;

Açık sorunlar

Tamlık için lambda ifade parametreleri için varsayılan değerler desteklenmeli mi?

Lambda ifadelerinde System.Diagnostics.ConditionalAttribute'in kullanılmasına, lambda ifadesinin koşullu olarak kullanılabileceği birkaç durum bulunduğundan izin verilmemeli mi?

([Conditional("DEBUG")] static (x, y) => Assert(x == y))(a, b); // ok?

function_type, sonuçta elde edilen temsilci türüne ek olarak derleyici API'sinden kullanılabilir mi?

Şu anda, çıkarım yapılan temsilci türü, parametre ve dönüş türleri geçerli tür bağımsız değişkenleri System.Action<> olduğunda ve 16'dan fazla parametre olmadığında System.Func<> veya kullanır. Beklenen Action<> veya Func<> türü eksikse, hata bildirilir. Bunun yerine, derleyici arity göz önünde bulundurmaksızın System.Action<> mu yoksa System.Func<> mi kullanmalıdır? Beklenen tür eksikse, bir temsilci türünü sentezle aksi takdirde?