注
この記事は機能仕様です。 仕様は、機能の設計ドキュメントとして機能します。 これには、提案された仕様の変更と、機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が最終決定され、現在の ECMA 仕様に組み込まれるまで公開されます。
機能の仕様と完成した実装の間には、いくつかの違いがある可能性があります。 これらの違いは、関連する 言語設計会議 (LDM) ノートでキャプチャされます。
機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。
チャンピオン号: https://github.com/dotnet/csharplang/issues/4934
概要
提案された変更:
- 属性を使用してラムダを許可する
- 明示的な戻り値の型を持つラムダを許可する
- ラムダとメソッド グループの自然なデリゲート型を推論する
モチベーション
ラムダの属性をサポートすると、メソッドとローカル関数とのパリティが提供されます。
明示的な戻り値の型のサポートは、明示的な型を指定できるラムダ パラメーターを使用して対称性を提供します。 明示的な戻り値の型を許可すると、入れ子になったラムダでコンパイラのパフォーマンスを制御することもできます。ここで、オーバーロードの解決では、シグネチャを決定するために現在ラムダ本体をバインドする必要があります。
ラムダ式とメソッド グループの自然型を使用すると、明示的なデリゲート型なしでラムダとメソッド グループを使用できるシナリオが増え、 var 宣言の初期化子としても使用できます。
ラムダとメソッド グループに明示的なデリゲート型を要求することは、お客様にとって摩擦点であり、 MapAction の最近の作業で ASP.NET を進める妨げとなっています。
ASP.NET MapAction 変更が提案されていません (MapAction() は System.Delegate 引数を受け取ります)。
[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 メソッド グループの自然な型を使用します。
[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name");
app.MapAction(GetTodo);
[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo;
app.MapAction(PostTodo);
ASP.NET MapAction ラムダ式の属性と自然型を使用します。
app.MapAction([HttpGet("/")] () => new Todo(Id: 0, Name: "Name"));
app.MapAction([HttpPost("/")] ([FromBody] Todo todo) => todo);
属性
ラムダ式とラムダ パラメーターに属性を追加できます。 メソッド属性とパラメーター属性の間のあいまいさを回避するには、属性を持つラムダ式でかっこで区切ったパラメーター リストを使用する必要があります。 パラメーター型は必要ありません。
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
同じ属性リスト内でコンマ区切りまたは個別の属性リストとして、複数の属性を指定できます。
var f = [A1, A2][A3] () => { }; // ok
var g = ([A1][A2, A3] int x) => x; // ok
属性は、構文で宣言delegate { }anonymous メソッドではサポートされていません。
f = [A] delegate { return 1; }; // syntax error at 'delegate'
f = delegate ([A] int x) { return x; }; // syntax error at '['
パーサーは、要素の割り当てを持つコレクション初期化子と、ラムダ式を持つコレクション初期化子を区別するために先を見ていきます。
var y = new C { [A] = x }; // ok: y[A] = x
var z = new C { [A] x => x }; // ok: z[0] = [A] x => x
パーサーは、 ?[ を条件付き要素アクセスの開始として扱います。
x = b ? [A]; // ok
y = b ? [A] () => { } : z; // syntax error at '('
ラムダ式またはラムダ パラメーターの属性は、ラムダにマップされるメソッドのメタデータに出力されます。
一般に、ラムダ式とローカル関数がソースからメタデータにどのようにマップされるかに依存しないようにする必要があります。 ラムダとローカル関数の出力方法は、コンパイラ のバージョン間で変更される可能性があります。
ここで提案される変更は、 Delegate 主導のシナリオを対象とします。
MethodInfo インスタンスに関連付けられているDelegateを調べて、明示的な属性や、コンパイラによって出力される追加のメタデータ (既定のパラメーターなど) を含むラムダ式またはローカル関数のシグネチャを判断することが有効である必要があります。
これにより、ASP.NET などのチームは、ラムダとローカル関数に対して通常のメソッドと同じ動作を使用できるようになります。
明示的な戻り値の型
明示的な戻り値の型は、かっこで区切ったパラメーター リストの前に指定できます。
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?
パーサーは、メソッド呼び出し T() とラムダ式 T () => eを区別するために先を見ていきます。
明示的な戻り値の型は、 delegate { } 構文で宣言された匿名メソッドではサポートされていません。
f = delegate int { return 1; }; // syntax error
f = delegate int (int x) { return x; }; // syntax error
メソッド型の推論では、明示的なラムダ戻り値の型から正確な推論を行う必要があります。
static void F<T>(Func<T, T> f) { ... }
F(int (i) => i); // Func<int, int>
ラムダ戻り値の型からデリゲートの戻り値の型への分散変換は許可されません (パラメーター型の同様の動作に一致します)。
Func<object> f1 = string () => null; // error
Func<object?> f2 = object () => x; // warning
パーサーでは、式内で ref 戻り値の型を持つラムダ式を、追加のかっこなしで使用できます。
d = ref int () => x; // d = (ref int () => x)
F(ref int () => x); // F((ref int () => x))
var は、ラムダ式の明示的な戻り値の型として使用できません。
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
ナチュラル (関数) 型
ananonymous 関数式 (§12.19) (lambda 式または anonym パラメーター型が明示的で、戻り値の型が明示的であるか、推論できる場合は、
method グループには、メソッド グループ内のすべての候補メソッドに共通のシグネチャがある場合、自然な型があります。 (メソッド グループに拡張メソッドが含まれている可能性がある場合、候補には、含まれる型とすべての拡張メソッド スコープが含まれます)。
匿名関数式またはメソッド グループの自然型は、function_type です。 function_typeは、パラメーターの型と ref の種類、戻り値の型と ref の種類のメソッド シグネチャを表します。 匿名関数式またはメソッド グループが同じシグネチャを持つ場合、同じ関数型を持ちます。
Function_types は、いくつかの特定のコンテキストでのみ使用されます。
- 暗黙的および明示的な変換
- メソッド型の推論 (§12.6.3) と最も一般的な型 (§12.6.3.15)
-
var初期化 子
function_typeはコンパイル時にのみ存在します。function_typesソースまたはメタデータには表示されません。
コンバージョン
function_typeFからは、暗黙的なfunction_type変換があります。
-
のパラメーターと戻り値の型がパラメーターと戻り値の型に分散変換可能かどうかを
GFします。G - の基底クラスまたはインターフェイスを
System.MulticastDelegateするにはSystem.MulticastDelegate -
System.Linq.Expressions.ExpressionまたはSystem.Linq.Expressions.LambdaExpression
匿名関数の式とメソッド グループには、式からデリゲート型と式ツリー型への変換が既に含まれています (匿名関数の変換§10.7およびメソッド グループ変換§10.8を参照)。 これらの変換は、厳密に型指定されたデリゲート型と式ツリー型に変換するのに十分です。 上記のfunction_type変換では、型System.Linq.Expressions.Expression変換が追加されます。
function_type以外の型からfunction_typeへの変換はありません。 ソースでfunction_typesを参照できないため、function_typesの明示的な変換はありません。
System.MulticastDelegate型または基本型またはインターフェイスへの変換は、匿名関数またはメソッド グループを適切なデリゲート型のインスタンスとして実現します。
System.Linq.Expressions.Expression<TDelegate>型または基本型への変換では、ラムダ式が適切なデリゲート型を持つ式ツリーとして実現されます。
Delegate d = delegate (object obj) { }; // Action<object>
Expression e = () => ""; // Expression<Func<string>>
object o = "".Clone; // Func<object>
Function_type 変換は、暗黙的または明示的な標準変換 §10.4 ではなく、ユーザー定義変換演算子が匿名関数またはメソッド グループに適用できるかどうかを判断する際には考慮されません。 ユーザー定義の変換の評価から §10.5.3:
変換演算子を適用するには、ソース型から演算子のオペランド型への標準変換 (§10.4) を実行でき、演算子の結果型からターゲット型への標準変換を実行できる必要があります。
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'
メソッド グループから object への暗黙的な変換に関する警告が報告されます。変換は有効ですが、意図しない可能性があるためです。
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
型の推定
型推論の既存の規則はほとんど変更されません ( §12.6.3 を参照)。 ただし、型推論の特定のフェーズに変更の一部があります。
第 1 フェーズ
第 1 フェーズ (§12.6.3.2) を使用すると、Tiがデリゲートまたは式ツリー型 (たとえば、Tiに制約された型パラメーター) でない場合でも、匿名関数をSystem.Delegateにバインドできます。
各メソッド引数
Eiに対して
Eiが匿名関数で、Tiがデリゲート型または式ツリー型である場合、explicit パラメーター型の推論はEiからTiに行われ、戻り値の型推論explicit が行われますはEiからTiに行われます。- それ以外の場合、
Eiに型Uがあり、xiが値パラメーターである場合は、より低い推論はfromTi行われます。- それ以外の場合、
Eiが型Uを持ち、xiがrefまたはoutパラメーターである場合は、exact 推論fromTi行われます。- それ以外の場合、この引数の推論は行われません。
明示的な戻り値の型の推論
explicit 戻り値の型推論は式
Eto次のようにTされます。
Eが明示的な戻り値の型Urを持つ匿名関数で、Tがデリゲート型または戻り値の型Vr式ツリー型である場合、exact 推論 (§12.6.3.9) はVrされます。
写真の
修正 (§12.6.3.12) により、他の変換が function_type 変換よりも優先されます。 (ラムダ式とメソッド グループ式は下限にのみ影響するため、 function_types の処理は下限でのみ必要です)。
一連の境界を持つ未固定型変数
Xiは、次のように固定されます。
- candidate 型のセット
Ujは、関数型ではない型がある場合、Xi場所の関数型の境界セット内のすべての型のセットが下限で無視されるために開始されます。- 次に、
Xiの各バインドを調べます。Uの正確なバインドされたXiごとに、Ujと同じではないすべての型Uが候補セットから削除されます。Uすべての型Xiの 各 lower boundUjの場合、 からの暗黙的変換は、候補一式から削除U。 存在しないすべての型UXiの上限UjごとにUへの暗黙的な変換が候補セットから削除されます。- 残りの候補の型
Uj他のすべての候補型への暗黙的な変換がある一意の型Vがある場合、XiはVに固定されます。- それ以外の場合、型の推論は失敗します。
最も一般的な型
最も一般的な型 (§12.6.3.15) は型推論の観点から定義されているため、上記の型推論の変更は、最も一般的な型にも適用されます。
var fs = new[] { (string s) => s.Length, (string s) => int.Parse(s) }; // Func<string, int>[]
var
関数型を持つ匿名関数とメソッド グループは、 var 宣言で初期化子として使用できます。
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>
破棄する割り当てでは、関数型は使用されません。
d = () => 0; // ok
_ = () => 1; // error
デリゲート型
パラメーター型 P1, ..., Pn および戻り値型 R を持つ匿名関数またはメソッド グループのデリゲート型は以下のとおりです。
- パラメーターまたは戻り値が値ではない場合、または 16 個を超えるパラメーターがある場合、またはパラメーターの型または戻り値のいずれかが有効な型引数 (
(int* p) => { }など) でない場合、デリゲートは匿名関数またはメソッド グループに一致するシグネチャを持つ合成されたinternal匿名デリゲート型であり、パラメーター名が 1 つのパラメーターの場合はarg1, ..., argnまたはarg。 -
Rがvoidの場合、デリゲート型はSystem.Action<P1, ..., Pn>。 - それ以外の場合、デリゲート型は
System.Func<P1, ..., Pn, R>。
コンパイラは、将来、より多くのシグネチャを System.Action<> および System.Func<> 型にバインドできる場合があります (たとえば、ref struct 型に型引数が許可されている場合)。
modopt() またはメソッド グループシグネチャの modreq() は、対応するデリゲート型では無視されます。
同じコンパイル内の 2 つの匿名関数またはメソッド グループで、同じパラメーター型と修飾子と同じ戻り値の型と修飾子を持つ合成デリゲート型が必要な場合、コンパイラは同じ合成デリゲート型を使用します。
オーバーロードの解決
より適切な関数メンバー (§12.6.4.3) が更新され、ラムダ式またはメソッド グループから推論された型に関係する変換や型引数がないメンバーが優先されます。
ベター関数メンバー
引数式のセット
Aとパラメータ型{E1, E2, ..., En}とMpを持つ 2 つの適用可能な関数メンバーMqと{P1, P2, ..., Pn}を含む引数リスト{Q1, Q2, ..., Qn}が与えられた場合、Mpは よりもMqであると定義されます。
- 引数ごとに、
ExからPxへの暗黙的な変換は function_type_conversionではなく、
Mpは非ジェネリック メソッドまたはMp型パラメーターが{X1, X2, ..., Xp}ジェネリック メソッドであり、各型パラメーターXi型引数は式または function_type以外の型から推論されます。- 少なくとも 1 つの引数の場合、
ExからQxへの暗黙的な変換はfunction_type_conversionか、Mqは型パラメーターが{Y1, Y2, ..., Yq}ジェネリック メソッドであり、型引数がYiから推論少なくとも 1 つの型パラメーターに対して行われます。- 各引数に対して、
ExからQxへの暗黙的な変換は、ExからPxへの暗黙的な変換よりも適していません。少なくとも 1 つの引数の場合、ExからPxへの変換は、ExからQxへの変換よりも優れています。
式 (§12.6.4.5) からの変換が改善され、ラムダ式またはメソッド グループからの推論された型を含まない変換が優先されます。
式からの変換の向上
式
C1から型Eに変換する暗黙的な変換T1と、式C2から型Eに変換する暗黙的な変換T2を考えると、C1は よりもC2優れた変換です。
C1は function_type_conversion ではなく、C2が function_type_conversionです。Eは非定数 interpolated_string_expression、C1は implicit_string_handler_conversion、T1は applicable_interpolated_string_handler_type、およびC2は implicit_string_handler_conversionではありません。EはT2と完全には一致せず、次のうち少なくとも 1 つが保持されます。
ET1と完全に一致します ( §12.6.4.5)T1は、T2(§12.6.4.7) よりも優れた変換ターゲットです
構文
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?
;
未処理の問題
完全化のためにラムダ式パラメーターの既定値をサポートする必要がありますか?
ラムダ式 System.Diagnostics.ConditionalAttribute 条件付きで使用できるシナリオはほとんどないため、ラムダ式では禁止する必要がありますか?
([Conditional("DEBUG")] static (x, y) => Assert(x == y))(a, b); // ok?
結果のデリゲート型に加えて、 function_type コンパイラ API から使用できる必要がありますか?
現在、推論されたデリゲート型は、パラメーターと戻り値の型が有効な型引数である場合にSystem.Action<>またはSystem.Func<>を使用 16 個以下のパラメーターがなく、予期されるAction<>またはFunc<>型がない場合は、エラーが報告されます。 代わりに、コンパイラはアリティに関係なく System.Action<> または System.Func<> を使用する必要がありますか? 予想される型がない場合は、デリゲート型を合成しますか?
C# feature specifications