From 49e3676024d06186f7796160b011ede9e858621a Mon Sep 17 00:00:00 2001 From: Charles Stoner Date: Thu, 11 Feb 2021 13:38:03 -0800 Subject: [PATCH 01/10] Lambda return types, attributes, and natural type --- proposals/csharp-10.0/lambda-improvements.md | 68 ++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 proposals/csharp-10.0/lambda-improvements.md diff --git a/proposals/csharp-10.0/lambda-improvements.md b/proposals/csharp-10.0/lambda-improvements.md new file mode 100644 index 0000000000..c476215be7 --- /dev/null +++ b/proposals/csharp-10.0/lambda-improvements.md @@ -0,0 +1,68 @@ +# Lambda improvements + +## Summary + +Support lambdas with explicit return type, attributes, and natural type. + +## Syntax + +```antlr +lambda_expression + : attribute_list* modifier* lambda_parameters (':' type)? '=>' ( block | body) + ; + +lambda_parameters + : parameter + | '(' (parameter (',' parameter)*)? ')' + ; + +parameter + : attribute_list* modifier* type? (identifier_token | '__arglist') equals_value_clause? + ; +``` +Attributes before a single parameter without parentheses bind to the parameter rather than the lambda. +```csharp +F([MyAttribute] x => x + 1); // [MyAttribute]x +F([MyAttribute] (y) => y + 1); // [MyAttribute]lambda +``` + +The return type may be a `ref` type. +```csharp +F(() : ref int => ref x); // ok +``` + +Explicit return types and attributes are not supported for anonymous methods declared with `delegate ()` syntax. +```csharp +F(delegate (int x) : int { return x + 1; }); // syntax error ': int' +F([MyAttribute] delegate (int y) { return y + 1; }); // syntax error 'delegate' +``` + +## Natural type + +Lambda expressions will have a natural type, allowing more scenarios where a lambda expression may be used without an explicit delegate type. + +The natural type is an `internal` anonymous `delegate` type. Anonymous delegate types will be shared within the compilation for lambdas with common signatures. The names of the delegate types, and the names of the parameters, are unspeakable. + +The natural type parameter types are the explicit parameter types, or `object` if implicit. +The natural type return type is the explicit return type, or if implicit, the best common type from the natural types of all `return` expressions, or `object`. + +The natural type allows `var` declarations with lambda expressions as initializers. +```csharp +var f1 = () => 1; // delegate int (); +var f2 = () => default; // delegate object (); +var f3 = () : string => null; // delegate string (); +var f4 = x => { }; // delegate void (object ); +var f5 = x => x; // delegate object (object ); +var f6 = (ref int x) => ref x; // delegate ref int (ref int ); +``` + +Since the natural type uses `object` for implicit types, this may result in boxing of parameters or return value. +```csharp +var f1 = x => x; // delegate object (object ); +int y = f1(1); // error: cannot convert 'object' to 'int' +var f2 = x : int => x; // error: cannot convert 'object' to 'int' +``` + +## Design meetings + +- _None_ From 611a934afce8815b5afa4e1b48caa023ed21a7e5 Mon Sep 17 00:00:00 2001 From: Charles Stoner Date: Wed, 17 Feb 2021 13:30:49 -0800 Subject: [PATCH 02/10] Updates and PR feedback --- proposals/csharp-10.0/lambda-improvements.md | 114 +++++++++++++------ 1 file changed, 77 insertions(+), 37 deletions(-) diff --git a/proposals/csharp-10.0/lambda-improvements.md b/proposals/csharp-10.0/lambda-improvements.md index c476215be7..a36585dce6 100644 --- a/proposals/csharp-10.0/lambda-improvements.md +++ b/proposals/csharp-10.0/lambda-improvements.md @@ -2,65 +2,105 @@ ## Summary -Support lambdas with explicit return type, attributes, and natural type. +Support lambdas with attributes, explicit return type, and natural type. -## Syntax - -```antlr -lambda_expression - : attribute_list* modifier* lambda_parameters (':' type)? '=>' ( block | body) - ; - -lambda_parameters - : parameter - | '(' (parameter (',' parameter)*)? ')' - ; +## Attributes +### Motivation +Support for attributes on lambdas would provide parity with methods and local functions. -parameter - : attribute_list* modifier* type? (identifier_token | '__arglist') equals_value_clause? - ; +### Design +Attributes may be added to lambda expressions. +```csharp +f = [MyAttribute] x => x; // [MyAttribute]lambda +f = [MyAttribute] (int x) => x; // [MyAttribute]lambda +f = [MyAttribute] static x => x; // [MyAttribute]lambda +f = [return: MyAttribute] () => 1; // [return: MyAttribute]lambda +``` +Attributes may be added to lambda parameters that are declared with explicit types. +```csharp +f = ([MyAttribute] x) => x; // syntax error +f = ([MyAttribute] int x) => x; // [MyAttribute]x ``` -Attributes before a single parameter without parentheses bind to the parameter rather than the lambda. + +Attributes are not supported for anonymous methods declared with `delegate { }` syntax. ```csharp -F([MyAttribute] x => x + 1); // [MyAttribute]x -F([MyAttribute] (y) => y + 1); // [MyAttribute]lambda +f = [MyAttribute] delegate { return 1; }; // syntax error +f = delegate ([MyAttribute] int x) { return x; }; // syntax error ``` -The return type may be a `ref` type. +## Explicit return type +### Motivation +Support for explicit return types would provide symmetry with lambda parameters where explicit types can be specified. + +Allowing explicit return types would also provide control over compiler performance in nested lambdas where overload resolution must bind the lambda body currently to determine the signature. + +### Design +An explicit return type may be specified after the parameter list. ```csharp -F(() : ref int => ref x); // ok +f = () : T => default; // () : T +f = x : int => 1; // : int +f = (ref int x) : ref int => ref x; // ref int : ref int +f = static _ : void => { }; // : void ``` -Explicit return types and attributes are not supported for anonymous methods declared with `delegate ()` syntax. +Explicit return types are not supported for anonymous methods declared with `delegate { }` syntax. ```csharp -F(delegate (int x) : int { return x + 1; }); // syntax error ': int' -F([MyAttribute] delegate (int y) { return y + 1; }); // syntax error 'delegate' +f = delegate : int { return 1; }; // syntax error +f = delegate (int x) : int { return x; }; // syntax error ``` ## Natural type +### Motivation +A natural type for lambda expressions and method groups will allow more scenarios where lambdas and method groups may be used without an explicit delegate type, including in `var` declarations. + +### Design +A lambda expression has a natural type if the parameters types are explicit and either the return type is explicit or there is a common type from the natural types of all `return` expressions in the body. Otherwise there is no natural type. -Lambda expressions will have a natural type, allowing more scenarios where a lambda expression may be used without an explicit delegate type. +A method group has a natural type if the method group contains a single method and the method or reduced extension method has no unbound type parameters. -The natural type is an `internal` anonymous `delegate` type. Anonymous delegate types will be shared within the compilation for lambdas with common signatures. The names of the delegate types, and the names of the parameters, are unspeakable. +If the lambda or method group has no more than 16 parameters and no return value, and all parameters are passed by value, the natural type will be `delegate void System.Action(P1, ..., Pn)` where `P1, ..., Pn` are the lambda parameter types. -The natural type parameter types are the explicit parameter types, or `object` if implicit. -The natural type return type is the explicit return type, or if implicit, the best common type from the natural types of all `return` expressions, or `object`. +If the lambda or method group has no more than 16 parameters and a by-value return type, and all parameters are passed by value, the natural type will be `delegate R System.Func(P1, ..., Pn)` where `P1, ..., Pn` are the lambda parameter types, and `R` is the lambda return type. + +Otherwise the natural type will be an `internal` anonymous `delegate` type with a signature matching the lambda signature. The names of the anonymous delegate types and the names of the parameters are unspeakable. + +Lambdas or method groups with natural types can be used as initializers in `var` declarations. -The natural type allows `var` declarations with lambda expressions as initializers. ```csharp -var f1 = () => 1; // delegate int (); -var f2 = () => default; // delegate object (); -var f3 = () : string => null; // delegate string (); -var f4 = x => { }; // delegate void (object ); -var f5 = x => x; // delegate object (object ); +var f1 = () => default; // error: no natural type +var f2 = x => { }; // error: no natural type +var f3 = x => x; // error: no natural type +var f4 = () => 1; // System.Func +var f5 = () : string => null; // System.Func var f6 = (ref int x) => ref x; // delegate ref int (ref int ); ``` -Since the natural type uses `object` for implicit types, this may result in boxing of parameters or return value. ```csharp -var f1 = x => x; // delegate object (object ); -int y = f1(1); // error: cannot convert 'object' to 'int' -var f2 = x : int => x; // error: cannot convert 'object' to 'int' +static void F1() { } +static void F1(this string s) { } +static void F2(this string s) { } + +var f7 = F1; // error: multiple methods +var f8 = "".F1; // System.Action +var f9 = F2; // System.Action +``` + +## Syntax + +```antlr +lambda_expression + : attribute_list* modifier* lambda_parameters (':' type)? '=>' (block | body) + ; + +lambda_parameters + : lambda_parameter + | '(' (lambda_parameter (',' lambda_parameter)*)? ')' + ; + +lambda_parameter + : identifier + | (attribute_list* modifier* type)? identifier equals_value_clause? + ; ``` ## Design meetings From 585c20bb4a91a482926b435d31ac824970601dc8 Mon Sep 17 00:00:00 2001 From: Charles Stoner Date: Fri, 19 Feb 2021 10:13:05 -0800 Subject: [PATCH 03/10] Updates and PR feedback --- proposals/csharp-10.0/lambda-improvements.md | 57 ++++++++++++-------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/proposals/csharp-10.0/lambda-improvements.md b/proposals/csharp-10.0/lambda-improvements.md index a36585dce6..f2149609f9 100644 --- a/proposals/csharp-10.0/lambda-improvements.md +++ b/proposals/csharp-10.0/lambda-improvements.md @@ -1,14 +1,18 @@ # Lambda improvements ## Summary - Support lambdas with attributes, explicit return type, and natural type. -## Attributes -### Motivation +## Motivation Support for attributes on lambdas would provide parity with methods and local functions. -### Design +Support for explicit return types would provide symmetry with lambda parameters where explicit types can be specified. +Allowing explicit return types would also provide control over compiler performance in nested lambdas where overload resolution must bind the lambda body currently to determine the signature. + +A natural type for lambda expressions and method groups will allow more scenarios where lambdas and method groups may be used without an explicit delegate type, including as initializers in `var` declarations. +Requiring explicit delegate types for lambdas and method groups has been a friction point for customers, and has become an impediment to progress in ASP.NET with recent work on [MapAction](https://github.com/dotnet/aspnetcore/pull/29878). + +## Attributes Attributes may be added to lambda expressions. ```csharp f = [MyAttribute] x => x; // [MyAttribute]lambda @@ -28,13 +32,9 @@ f = [MyAttribute] delegate { return 1; }; // syntax error f = delegate ([MyAttribute] int x) { return x; }; // syntax error ``` -## Explicit return type -### Motivation -Support for explicit return types would provide symmetry with lambda parameters where explicit types can be specified. - -Allowing explicit return types would also provide control over compiler performance in nested lambdas where overload resolution must bind the lambda body currently to determine the signature. +Attributes on the lambda or lambda parameters will be emitted to metadata on the method that maps to the lambda. (If the lambda does not require a closure class, the lambda is emitted as a method on the containing type. Otherwise the lambda is as a method on the generated closure class.) -### Design +## Explicit return type An explicit return type may be specified after the parameter list. ```csharp f = () : T => default; // () : T @@ -50,21 +50,17 @@ f = delegate (int x) : int { return x; }; // syntax error ``` ## Natural type -### Motivation -A natural type for lambda expressions and method groups will allow more scenarios where lambdas and method groups may be used without an explicit delegate type, including in `var` declarations. - -### Design A lambda expression has a natural type if the parameters types are explicit and either the return type is explicit or there is a common type from the natural types of all `return` expressions in the body. Otherwise there is no natural type. A method group has a natural type if the method group contains a single method and the method or reduced extension method has no unbound type parameters. +Lambdas or method groups with natural types can be used as initializers in `var` declarations. + If the lambda or method group has no more than 16 parameters and no return value, and all parameters are passed by value, the natural type will be `delegate void System.Action(P1, ..., Pn)` where `P1, ..., Pn` are the lambda parameter types. If the lambda or method group has no more than 16 parameters and a by-value return type, and all parameters are passed by value, the natural type will be `delegate R System.Func(P1, ..., Pn)` where `P1, ..., Pn` are the lambda parameter types, and `R` is the lambda return type. -Otherwise the natural type will be an `internal` anonymous `delegate` type with a signature matching the lambda signature. The names of the anonymous delegate types and the names of the parameters are unspeakable. - -Lambdas or method groups with natural types can be used as initializers in `var` declarations. +Otherwise the natural type will be a synthesized `internal` anonymous `delegate` type with a signature that matches the lambda or method group. ```csharp var f1 = () => default; // error: no natural type @@ -72,19 +68,36 @@ var f2 = x => { }; // error: no natural type var f3 = x => x; // error: no natural type var f4 = () => 1; // System.Func var f5 = () : string => null; // System.Func -var f6 = (ref int x) => ref x; // delegate ref int (ref int ); ``` ```csharp static void F1() { } -static void F1(this string s) { } +static void F1(this T t) { } static void F2(this string s) { } -var f7 = F1; // error: multiple methods -var f8 = "".F1; // System.Action -var f9 = F2; // System.Action +var f6 = F1; // error: multiple methods +var f7 = "".F1; // System.Action +var f8 = F2; // System.Action +``` + +### Anonymous delegate type +If synthesized delegate types are required, the compiler generates generic anonymous delegate types that are shared across all anonymous delegates in the module that have the same number of parameters and same parameter ref kinds. + +The names of the synthesized delegate types and the names of the parameters are unspeakable. + +The anonymous delegate types are not co- or contra-variant unlike the delegates constructed from `System.Action<>` and `System.Func<>`. + +The natural type of a method group does not include any `modopt()` or `modreq()` on the method group. + +`fA` and `fB` share a common generic type `internal delegate ref R (ref P1 )` below: +```csharp +var f9 = (ref int i) => { }; // delegate void (ref int ); +var fA = (ref char c) => ref c; // delegate ref char (ref char ); +var fB = (ref int i) => ref i; // delegate ref int (ref int ); ``` +_Issue: Is it necessary to use `System.Action<>` and `System.Func<>` for the delegate types or should the compiler always generate an anonymous delegate type? There is an observable difference since the anonymous delegate types are not co- or contra-variant._ + ## Syntax ```antlr From 5487f6e40a6185518dab70e2e3d3cab4a532c9ad Mon Sep 17 00:00:00 2001 From: Charles Stoner Date: Fri, 19 Feb 2021 11:21:57 -0800 Subject: [PATCH 04/10] PR feedback --- proposals/csharp-10.0/lambda-improvements.md | 47 +++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/proposals/csharp-10.0/lambda-improvements.md b/proposals/csharp-10.0/lambda-improvements.md index f2149609f9..da8b1d57fe 100644 --- a/proposals/csharp-10.0/lambda-improvements.md +++ b/proposals/csharp-10.0/lambda-improvements.md @@ -1,7 +1,10 @@ # Lambda improvements ## Summary -Support lambdas with attributes, explicit return type, and natural type. +Proposed changes: +1. Allow lambdas with attributes +2. Allow lambdas with explicit return type +3. Infer a natural delegate type for lambdas and method groups ## Motivation Support for attributes on lambdas would provide parity with methods and local functions. @@ -32,13 +35,13 @@ f = [MyAttribute] delegate { return 1; }; // syntax error f = delegate ([MyAttribute] int x) { return x; }; // syntax error ``` -Attributes on the lambda or lambda parameters will be emitted to metadata on the method that maps to the lambda. (If the lambda does not require a closure class, the lambda is emitted as a method on the containing type. Otherwise the lambda is as a method on the generated closure class.) +Attributes on the lambda or lambda parameters will be emitted to metadata on the method that maps to the lambda. ## Explicit return type An explicit return type may be specified after the parameter list. ```csharp f = () : T => default; // () : T -f = x : int => 1; // : int +f = x : short => 1; // : short f = (ref int x) : ref int => ref x; // ref int : ref int f = static _ : void => { }; // : void ``` @@ -49,10 +52,10 @@ f = delegate : int { return 1; }; // syntax error f = delegate (int x) : int { return x; }; // syntax error ``` -## Natural type +## Natural delegate type A lambda expression has a natural type if the parameters types are explicit and either the return type is explicit or there is a common type from the natural types of all `return` expressions in the body. Otherwise there is no natural type. -A method group has a natural type if the method group contains a single method and the method or reduced extension method has no unbound type parameters. +A method group has a natural type if the method group contains a single method and the method has no unbound type parameters. Lambdas or method groups with natural types can be used as initializers in `var` declarations. @@ -96,7 +99,39 @@ var fA = (ref char c) => ref c; // delegate ref char (re var fB = (ref int i) => ref i; // delegate ref int (ref int ); ``` -_Issue: Is it necessary to use `System.Action<>` and `System.Func<>` for the delegate types or should the compiler always generate an anonymous delegate type? There is an observable difference since the anonymous delegate types are not co- or contra-variant._ +_Issue: Should the compiler always generate anonymous delegate types rather than using `System.Action<>` and `System.Func<>`? There are observable differences: 1. the anonymous delegate types are not co- or contra-variant; and 2. using a synthesized delegate type would mean the following assignment to `y` would fail: `var x = () => { }; Action y = x;`._ + +### Implicit conversion to `System.Delegate` +A consequence of inferring a natural type is that lambda expressions and method groups with natural type are implicitly convertible to `System.Delegate`. +```csharp +static void Invoke(Func f) { } +static void Invoke(Delegate d) { } + +static string GetString() => ""; +static int GetInt() => 0; + +Invoke(() => ""); // Invoke(Func) +Invoke(() => 0); // Invoke(Delegate) [new] + +Invoke(GetString); // Invoke(Func) +Invoke(GetInt); // Invoke(Delegate) [new] +``` + +To avoid a breaking change, overload resolution will be updated to prefer lambda and method group conversions that do not use the natural type. +_The example below demonstrates the tie-breaking rule for lambdas. Is there an equivalent example for method groups?_ +```csharp +static void Execute(Expression> e) { } +static void Execute(Delegate d) { } + +static string GetString() => ""; +static int GetInt() => 0; + +Execute(() => ""); // Execute(Expression>) [tie-breaker] +Execute(() => 0); // Execute(Delegate) + +Execute(GetString); // Execute(Delegate) +Execute(GetInt); // Execute(Delegate) +``` ## Syntax From 1592d420990736a229bbfd045a04fd9ac00a44ec Mon Sep 17 00:00:00 2001 From: Charles Stoner Date: Fri, 26 Feb 2021 16:52:56 -0800 Subject: [PATCH 05/10] Update delegate type description --- proposals/csharp-10.0/lambda-improvements.md | 38 ++++++++------------ 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/proposals/csharp-10.0/lambda-improvements.md b/proposals/csharp-10.0/lambda-improvements.md index da8b1d57fe..8b750ac643 100644 --- a/proposals/csharp-10.0/lambda-improvements.md +++ b/proposals/csharp-10.0/lambda-improvements.md @@ -53,17 +53,27 @@ f = delegate (int x) : int { return x; }; // syntax error ``` ## Natural delegate type -A lambda expression has a natural type if the parameters types are explicit and either the return type is explicit or there is a common type from the natural types of all `return` expressions in the body. Otherwise there is no natural type. +A lambda expression has a natural type if the parameters types are explicit and either the return type is explicit or there is a common type from the natural types of all `return` expressions in the body. + +The natural type is a delegate type where the parameter types are the explicit lambda parameter types and the return type `R` is: +- if the lambda return type is explicit, that type is used; +- if the lambda has no return expressions, the return type is `void` or `System.Threading.Tasks.Task` if `async`; +- if the common type from the natural type of all `return` expressions in the body is the type `R0`, the return type is `R0` or `System.Threading.Tasks.Task` if `async`. A method group has a natural type if the method group contains a single method and the method has no unbound type parameters. -Lambdas or method groups with natural types can be used as initializers in `var` declarations. +The delegate type for the lambda or method group and parameter types `P1, ..., Pn` and return type `R` is: +- if any parameter or return value is not by value, or there are more than 16 parameters, or any of the parameter types or return are not valid type arguments (say, `(int* p) => { }`), then the delegate is a synthesized `internal` anonymous delegate type with signature that matches the lambda or method group, and with parameter names `arg1, ..., argn` or `arg` if a single parameter; +- if `R` is `void`, then the delegate type is `System.Action`; +- otherwise the delegate type is `System.Func`. + +`modopt()` or `modreq()` in the method group signature are ignored in the corresponding delegate type. -If the lambda or method group has no more than 16 parameters and no return value, and all parameters are passed by value, the natural type will be `delegate void System.Action(P1, ..., Pn)` where `P1, ..., Pn` are the lambda parameter types. +If synthesized delegate types are required, the compiler will attempt to reuse delegate types across multiple use sites. The compiler could generate generic delegate types parameterized by parameter types and return type similar to the generic types synthesized for anonymous types. But reuse might be limited to simply reusing delegate types when lambda or method group signatures match exactly. -If the lambda or method group has no more than 16 parameters and a by-value return type, and all parameters are passed by value, the natural type will be `delegate R System.Func(P1, ..., Pn)` where `P1, ..., Pn` are the lambda parameter types, and `R` is the lambda return type. +The anonymous delegate types will not be co- or contra-variant unlike the delegates constructed from `System.Action<>` and `System.Func<>`. -Otherwise the natural type will be a synthesized `internal` anonymous `delegate` type with a signature that matches the lambda or method group. +Lambdas or method groups with natural types can be used as initializers in `var` declarations. ```csharp var f1 = () => default; // error: no natural type @@ -83,24 +93,6 @@ var f7 = "".F1; // System.Action var f8 = F2; // System.Action ``` -### Anonymous delegate type -If synthesized delegate types are required, the compiler generates generic anonymous delegate types that are shared across all anonymous delegates in the module that have the same number of parameters and same parameter ref kinds. - -The names of the synthesized delegate types and the names of the parameters are unspeakable. - -The anonymous delegate types are not co- or contra-variant unlike the delegates constructed from `System.Action<>` and `System.Func<>`. - -The natural type of a method group does not include any `modopt()` or `modreq()` on the method group. - -`fA` and `fB` share a common generic type `internal delegate ref R (ref P1 )` below: -```csharp -var f9 = (ref int i) => { }; // delegate void (ref int ); -var fA = (ref char c) => ref c; // delegate ref char (ref char ); -var fB = (ref int i) => ref i; // delegate ref int (ref int ); -``` - -_Issue: Should the compiler always generate anonymous delegate types rather than using `System.Action<>` and `System.Func<>`? There are observable differences: 1. the anonymous delegate types are not co- or contra-variant; and 2. using a synthesized delegate type would mean the following assignment to `y` would fail: `var x = () => { }; Action y = x;`._ - ### Implicit conversion to `System.Delegate` A consequence of inferring a natural type is that lambda expressions and method groups with natural type are implicitly convertible to `System.Delegate`. ```csharp From d5f68a7b0de8b45c2034842fde718e150d92b23e Mon Sep 17 00:00:00 2001 From: Charles Stoner Date: Mon, 1 Mar 2021 11:54:17 -0800 Subject: [PATCH 06/10] Add ASP.NET example --- proposals/csharp-10.0/lambda-improvements.md | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/proposals/csharp-10.0/lambda-improvements.md b/proposals/csharp-10.0/lambda-improvements.md index 8b750ac643..b52c40ec60 100644 --- a/proposals/csharp-10.0/lambda-improvements.md +++ b/proposals/csharp-10.0/lambda-improvements.md @@ -15,6 +15,30 @@ Allowing explicit return types would also provide control over compiler performa A natural type for lambda expressions and method groups will allow more scenarios where lambdas and method groups may be used without an explicit delegate type, including as initializers in `var` declarations. Requiring explicit delegate types for lambdas and method groups has been a friction point for customers, and has become an impediment to progress in ASP.NET with recent work on [MapAction](https://github.com/dotnet/aspnetcore/pull/29878). +[ASP.NET MapAction](https://github.com/dotnet/aspnetcore/pull/29878) without proposed changes (`MapAction()` takes a `System.Delegate` argument): +```csharp +[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name"); +app.MapAction((Func)GetTodo); + +[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo); +app.MapAction((Func)PostTodo); +``` + +[ASP.NET MapAction](https://github.com/dotnet/aspnetcore/pull/29878) with natural types for method groups: +```csharp +[HttpGet("/")] Todo GetTodo() => new(Id: 0, Name: "Name"); +app.MapAction(GetTodo); + +[HttpPost("/")] Todo PostTodo([FromBody] Todo todo) => todo); +app.MapAction(PostTodo); +``` + +[ASP.NET MapAction](https://github.com/dotnet/aspnetcore/pull/29878) with attributes and natural types for lambda expressions: +```csharp +app.MapAction([HttpGet("/")] () => new Todo(Id: 0, Name: "Name")); +app.MapAction([HttpPost("/")] ([FromBody] Todo todo) => todo); +``` + ## Attributes Attributes may be added to lambda expressions. ```csharp From c7c6ada7e1f547ec529f6afade8b068d1a453fde Mon Sep 17 00:00:00 2001 From: Charles Stoner Date: Tue, 2 Mar 2021 10:36:43 -0800 Subject: [PATCH 07/10] Incorporate feedback --- proposals/csharp-10.0/lambda-improvements.md | 36 +++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/proposals/csharp-10.0/lambda-improvements.md b/proposals/csharp-10.0/lambda-improvements.md index b52c40ec60..3ee91bb725 100644 --- a/proposals/csharp-10.0/lambda-improvements.md +++ b/proposals/csharp-10.0/lambda-improvements.md @@ -13,6 +13,7 @@ Support for explicit return types would provide symmetry with lambda parameters Allowing explicit return types would also provide control over compiler performance in nested lambdas where overload resolution must bind the lambda body currently to determine the signature. A natural type for lambda expressions and method groups will allow more scenarios where lambdas and method groups may be used without an explicit delegate type, including as initializers in `var` declarations. + Requiring explicit delegate types for lambdas and method groups has been a friction point for customers, and has become an impediment to progress in ASP.NET with recent work on [MapAction](https://github.com/dotnet/aspnetcore/pull/29878). [ASP.NET MapAction](https://github.com/dotnet/aspnetcore/pull/29878) without proposed changes (`MapAction()` takes a `System.Delegate` argument): @@ -59,7 +60,13 @@ f = [MyAttribute] delegate { return 1; }; // syntax error f = delegate ([MyAttribute] int x) { return x; }; // syntax error ``` -Attributes on the lambda or lambda parameters will be emitted to metadata on the method that maps to the lambda. +Attributes on the lambda expression or lambda parameters will be emitted to metadata on the method that maps to the lambda. + +In general, customers should not depend on how lambda expressions and local functions map from source to metadata. How lambdas and local functions are emitted can, and has, changed between compiler versions. + +The changes proposed here are targeted at the `Delegate` driven scenario. +It should be valid to inspect the `MethodInfo` associated with a `Delegate` instance to determine the signature of the lambda expression or local function including any explicit attributes and additional metadata emitted by the compiler such as default parameters. +This allows teams such as ASP.NET to make available the same behaviors for lambdas and local functions as ordinary methods. ## Explicit return type An explicit return type may be specified after the parameter list. @@ -84,7 +91,9 @@ The natural type is a delegate type where the parameter types are the explicit l - if the lambda has no return expressions, the return type is `void` or `System.Threading.Tasks.Task` if `async`; - if the common type from the natural type of all `return` expressions in the body is the type `R0`, the return type is `R0` or `System.Threading.Tasks.Task` if `async`. -A method group has a natural type if the method group contains a single method and the method has no unbound type parameters. +A method group has a natural type if the method group contains a single method. + +_A method group might refer to extension methods. Normally method group resolution searches for extension methods lazily, only iterating through successive namespace scopes until extension methods are found that match the target type. But to determine that a method group contains a single method the compiler may need to search all namespace scopes. To minimize unnecessary binding, perhaps natural type should be calculated only in cases where there is no target type - that is, only calculate the natural type in cases where it is needed._ The delegate type for the lambda or method group and parameter types `P1, ..., Pn` and return type `R` is: - if any parameter or return value is not by value, or there are more than 16 parameters, or any of the parameter types or return are not valid type arguments (say, `(int* p) => { }`), then the delegate is a synthesized `internal` anonymous delegate type with signature that matches the lambda or method group, and with parameter names `arg1, ..., argn` or `arg` if a single parameter; @@ -95,8 +104,6 @@ The delegate type for the lambda or method group and parameter types `P1, ..., P If synthesized delegate types are required, the compiler will attempt to reuse delegate types across multiple use sites. The compiler could generate generic delegate types parameterized by parameter types and return type similar to the generic types synthesized for anonymous types. But reuse might be limited to simply reusing delegate types when lambda or method group signatures match exactly. -The anonymous delegate types will not be co- or contra-variant unlike the delegates constructed from `System.Action<>` and `System.Func<>`. - Lambdas or method groups with natural types can be used as initializers in `var` declarations. ```csharp @@ -117,6 +124,15 @@ var f7 = "".F1; // System.Action var f8 = F2; // System.Action ``` +The synthesized delegate types are implicitly co- and contra-variant. +```csharp +var fA = (IEnumerable e, ref int i) => { }; // void DA$(IEnumerable, ref int); +fA = (IEnumerable e, ref int i) => { }; // ok + +var fB = (IEnumerable e, ref int i) => { }; // void DB$(IEnumerable, ref int); +fB = (IEnumerable e, ref int i) => { }; // error: parameter type mismatch +``` + ### Implicit conversion to `System.Delegate` A consequence of inferring a natural type is that lambda expressions and method groups with natural type are implicitly convertible to `System.Delegate`. ```csharp @@ -133,6 +149,14 @@ Invoke(GetString); // Invoke(Func) Invoke(GetInt); // Invoke(Delegate) [new] ``` +If a natural type cannot be inferred, there is no implicit conversion to `System.Delegate`. +```csharp +static void Invoke(Delegate d) { } + +Invoke(Console.WriteLine); // error: cannot to 'Delegate'; multiple candidate methods +Invoke(x => x); // error: cannot to 'Delegate'; no natural type for 'x' +``` + To avoid a breaking change, overload resolution will be updated to prefer lambda and method group conversions that do not use the natural type. _The example below demonstrates the tie-breaking rule for lambdas. Is there an equivalent example for method groups?_ ```csharp @@ -167,6 +191,10 @@ lambda_parameter ; ``` +_Does the `: type` return type syntax introduce ambiguities with `?:` that cannot be resolved easily?_ + +_Should we allow attributes on parameters without explicit types, such as `([MyAttribute] x) => { }`? (We don't allow modifiers on parameters without explicit types, such as `(ref x) => { }`.)_ + ## Design meetings - _None_ From 9215564fe97b2a9f8e7062944bd079ecdcc6b7d8 Mon Sep 17 00:00:00 2001 From: Charles Stoner Date: Wed, 3 Mar 2021 09:19:39 -0800 Subject: [PATCH 08/10] Misc. --- proposals/csharp-10.0/lambda-improvements.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/proposals/csharp-10.0/lambda-improvements.md b/proposals/csharp-10.0/lambda-improvements.md index 3ee91bb725..020f14d6b5 100644 --- a/proposals/csharp-10.0/lambda-improvements.md +++ b/proposals/csharp-10.0/lambda-improvements.md @@ -93,7 +93,7 @@ The natural type is a delegate type where the parameter types are the explicit l A method group has a natural type if the method group contains a single method. -_A method group might refer to extension methods. Normally method group resolution searches for extension methods lazily, only iterating through successive namespace scopes until extension methods are found that match the target type. But to determine that a method group contains a single method the compiler may need to search all namespace scopes. To minimize unnecessary binding, perhaps natural type should be calculated only in cases where there is no target type - that is, only calculate the natural type in cases where it is needed._ +A method group might refer to extension methods. Normally method group resolution searches for extension methods lazily, only iterating through successive namespace scopes until extension methods are found that match the target type. But to determine the natural type will require searching all namespace scopes. _To minimize unnecessary binding, perhaps natural type should be calculated only in cases where there is no target type - that is, only calculate the natural type in cases where it is needed._ The delegate type for the lambda or method group and parameter types `P1, ..., Pn` and return type `R` is: - if any parameter or return value is not by value, or there are more than 16 parameters, or any of the parameter types or return are not valid type arguments (say, `(int* p) => { }`), then the delegate is a synthesized `internal` anonymous delegate type with signature that matches the lambda or method group, and with parameter names `arg1, ..., argn` or `arg` if a single parameter; @@ -157,7 +157,7 @@ Invoke(Console.WriteLine); // error: cannot to 'Delegate'; multiple candidate me Invoke(x => x); // error: cannot to 'Delegate'; no natural type for 'x' ``` -To avoid a breaking change, overload resolution will be updated to prefer lambda and method group conversions that do not use the natural type. +To avoid a breaking change, overload resolution will be updated to prefer strongly-typed delegates and expressions over `System.Delegate`. _The example below demonstrates the tie-breaking rule for lambdas. Is there an equivalent example for method groups?_ ```csharp static void Execute(Expression> e) { } @@ -167,10 +167,10 @@ static string GetString() => ""; static int GetInt() => 0; Execute(() => ""); // Execute(Expression>) [tie-breaker] -Execute(() => 0); // Execute(Delegate) +Execute(() => 0); // Execute(Delegate) [new] -Execute(GetString); // Execute(Delegate) -Execute(GetInt); // Execute(Delegate) +Execute(GetString); // Execute(Delegate) [new] +Execute(GetInt); // Execute(Delegate) [new] ``` ## Syntax From 435ebd1d5365be0a11dcc51ec9806433b2b6d368 Mon Sep 17 00:00:00 2001 From: Charles Stoner Date: Fri, 5 Mar 2021 12:58:52 -0800 Subject: [PATCH 09/10] PR feedback --- proposals/csharp-10.0/lambda-improvements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/csharp-10.0/lambda-improvements.md b/proposals/csharp-10.0/lambda-improvements.md index 020f14d6b5..b48b465094 100644 --- a/proposals/csharp-10.0/lambda-improvements.md +++ b/proposals/csharp-10.0/lambda-improvements.md @@ -102,7 +102,7 @@ The delegate type for the lambda or method group and parameter types `P1, ..., P `modopt()` or `modreq()` in the method group signature are ignored in the corresponding delegate type. -If synthesized delegate types are required, the compiler will attempt to reuse delegate types across multiple use sites. The compiler could generate generic delegate types parameterized by parameter types and return type similar to the generic types synthesized for anonymous types. But reuse might be limited to simply reusing delegate types when lambda or method group signatures match exactly. +If two lambda expressions or method groups in the same compilation require synthesized delegate types with the same parameter types and modifiers and the same return type and modifiers, the compiler will use the same synthesized delegate type. Lambdas or method groups with natural types can be used as initializers in `var` declarations. From 3c427dee67689f4e7f5b45b593d95b00ca0b38c0 Mon Sep 17 00:00:00 2001 From: Charles Stoner Date: Fri, 5 Mar 2021 16:03:24 -0800 Subject: [PATCH 10/10] Add open issue --- proposals/csharp-10.0/lambda-improvements.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/proposals/csharp-10.0/lambda-improvements.md b/proposals/csharp-10.0/lambda-improvements.md index b48b465094..73a9183f0d 100644 --- a/proposals/csharp-10.0/lambda-improvements.md +++ b/proposals/csharp-10.0/lambda-improvements.md @@ -48,6 +48,9 @@ f = [MyAttribute] (int x) => x; // [MyAttribute]lambda f = [MyAttribute] static x => x; // [MyAttribute]lambda f = [return: MyAttribute] () => 1; // [return: MyAttribute]lambda ``` + +_Should parentheses be required for the parameter list if attributes are added to the entire expression? (Should `[MyAttribute] x => x` be disallowed? If so, what about `[MyAttribute] static x => x`?)_ + Attributes may be added to lambda parameters that are declared with explicit types. ```csharp f = ([MyAttribute] x) => x; // syntax error