Skip to content

Commit

Permalink
Add docs updates for simpler lambda parameter modifiers
Browse files Browse the repository at this point in the history
Fixes #44887 Publish the speclet and update the language reference to allow modifiers on implicitly typed lambda expressions.
  • Loading branch information
BillWagner committed Feb 13, 2025
1 parent c179223 commit 854ccdc
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 18 deletions.
4 changes: 3 additions & 1 deletion docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@
"_csharplang/proposals/csharp-11.0/*.md": "09/30/2022",
"_csharplang/proposals/csharp-12.0/*.md": "08/15/2023",
"_csharplang/proposals/csharp-13.0/*.md": "10/31/2024",
"_csharplang/proposals/*.md": "10/31/2024",
"_csharplang/proposals/*.md": "02/14/2025",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "11/08/2022",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md": "11/08/2023",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md": "11/09/2024",
Expand Down Expand Up @@ -684,6 +684,7 @@
"_csharplang/proposals/field-keyword.md": "The `field` contextual keyword",
"_csharplang/proposals/unbound-generic-types-in-nameof.md": "Unbound generic types in `nameof`",
"_csharplang/proposals/first-class-span-types.md": "First-class span types",
"_csharplang/proposals/simple-lambda-parameters-with-modifiers.md": "Simple lambda parameters with modifiers",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "C# compiler breaking changes since C# 10",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md": "C# compiler breaking changes since C# 11",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md": "C# compiler breaking changes since C# 12",
Expand Down Expand Up @@ -807,6 +808,7 @@
"_csharplang/proposals/field-keyword.md": "This proposal introduces a new keyword, `field`, that accesses the compiler generated backing field in a property accessor.",
"_csharplang/proposals/unbound-generic-types-in-nameof.md": "This proposal introduces the ability to use unbound generic types such as `List<>` in `nameof` expressions. The type argument isn't required.",
"_csharplang/proposals/first-class-span-types.md": "This proposal provides several implicit conversions to `Span<T>` and `ReadOnlySpan<T>` that enable library authors to have fewer overloads and developers to write code that resolves to faster Span based APIs",
"_csharplang/proposals/simle-lambda-parameters-with-modifiers.md": "This proposal provides allows lambda parmaeters to be declared with modifiers without requiring their type names. You can add modifiers like `ref` and `out` to lambda parameters without specifying their type.",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "Learn about any breaking changes since the initial release of C# 10 and included in C# 11",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 8.md": "Learn about any breaking changes since the initial release of C# 11 and included in C# 12",
"_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 9.md": "Learn about any breaking changes since the initial release of C# 12 and included in C# 13",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ f1_keywords:
- "CS9225"
- "CS9227"
- "CS9228"
- "CS9272"
helpviewer_keywords:
- "CS0225"
- "CS0231"
Expand All @@ -33,7 +34,8 @@ helpviewer_keywords:
- "CS9225"
- "CS9227"
- "CS9228"
ms.date: 05/20/2024
- "CS9272"
ms.date: 02/13/2025
---
# Errors and warnings related to the `params` modifier on method parameters

Expand All @@ -57,6 +59,7 @@ That's by design. The text closely matches the text of the compiler error / warn
- [**CS9225**](#other-params-errors): *Constructor leaves required member uninitialized.*
- [**CS9227**](#parameter-and-argument-type-rules): *Type does not contain a definition for a suitable instance `Add` method.*
- [**CS9228**](#parameter-and-argument-type-rules): *Non-array params collection type must have an applicable constructor that can be called with no arguments.*
- [**CS9272**](#other-params-errors): *Implicitly typed lambda parameter cannot have the 'params' modifier.*

## Method declaration rules

Expand Down Expand Up @@ -101,6 +104,7 @@ The following errors indicate other issues with using the `params` modifier:
- **CS9223**: *Creation of params collection results in an infinite chain of invocation of constructor.*
- **CS9224**: *Method cannot be less visible than the member with params collection.*
- **CS9225**: *Constructor leaves required member uninitialized.*
- **CS9272**: *Implicitly typed lambda parameter cannot have the 'params' modifier.*

A method that implements an interface must include the `params` modifier if and only if the interface member has the `params` modifier. Similarly, either both declarations of a `partial` method must include the `params` modifier, or none can include the `params` modifier.

Expand All @@ -111,6 +115,7 @@ The compiler generates one of the final three errors in the preceding list when
- The compiler emits **CS9223** when the code emitted to create the collection also contains a params collection of the same type. Typically, the `Add` method takes a `params` collection of the same type.
- The compiler emits **CS9224** when the `Create` method for the collection type is less accessible than the method that takes the `params` parameter of the collection type.
- The compiler emits **CS9225** when the collection type has a required member and the parameterless constructor doesn't initialize that member and have the <xref:System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute?displayProperty=nameWithType> on the parameterless constructor.
- The compiler emits **CS9272** when you've used the `params` modifier without type information on a lambda expression. You must specify the types for all lambda expression parameters to use the `params` modifier.

## See also

Expand Down
20 changes: 10 additions & 10 deletions docs/csharp/language-reference/operators/lambda-expressions.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Lambda expressions - Lambda expressions and anonymous functions"
description: C# lambda expressions that are used to create anonymous functions and expression bodied members.
ms.date: 11/22/2024
ms.date: 02/13/2025
helpviewer_keywords:
- "lambda expressions [C#]"
- "outer variables [C#]"
Expand Down Expand Up @@ -49,7 +49,7 @@ A lambda expression with an expression on the right side of the `=>` operator is
(input-parameters) => expression
```

The body of an expression lambda can consist of a method call. However, if you're creating [expression trees](../../advanced-topics/expression-trees/index.md) that are evaluated outside the context of the .NET Common Language Runtime (CLR), such as in SQL Server, you shouldn't use method calls in lambda expressions. The methods have no meaning outside the context of the .NET Common Language Runtime (CLR).
The body of an expression lambda can consist of a method call. However, when creating [expression trees](../../advanced-topics/expression-trees/index.md) evaluated by a query provider, limit method calls to those methods recognized by the query provider. Otherwise, the query provider can't replicate the method's function.

## Statement lambdas

Expand Down Expand Up @@ -79,11 +79,11 @@ Two or more input parameters are separated by commas:

:::code language="csharp" source="snippets/lambda-expressions/GeneralExamples.cs" id="SnippetTwoParameters":::

Sometimes the compiler can't infer the types of input parameters. You can specify the types explicitly as shown in the following example:
The compiler typically infers the types for parameters to lambda expressions, referred to as an *implicitly typed parameter list*. You can specify the types explicitly, referred to as an *explicitly typed parameter list*. An explicitly typed parameter list is shown in the following example. :

:::code language="csharp" source="snippets/lambda-expressions/GeneralExamples.cs" id="SnippetExplicitlyTypedParameters":::

Input parameter types must be all explicit or all implicit; otherwise, a [CS0748](../compiler-messages/lambda-expression-errors.md#lambda-expression-parameters-and-returns) compiler error occurs.
Input parameter types must be all explicit or all implicit; otherwise, a [CS0748](../compiler-messages/lambda-expression-errors.md#lambda-expression-parameters-and-returns) compiler error occurs. Before C# 14, you must include the explicit type on a parameter if it has any modifiers, such as `ref` or `out`. In C# 14, that restriction is removed. However, you must still declare the type if you use the `params` modifier.

You can use [discards](../../fundamentals/functional/discards.md) to specify two or more input parameters of a lambda expression that aren't used in the expression:

Expand All @@ -92,13 +92,13 @@ You can use [discards](../../fundamentals/functional/discards.md) to specify two
Lambda discard parameters can be useful when you use a lambda expression to [provide an event handler](../../programming-guide/events/how-to-subscribe-to-and-unsubscribe-from-events.md).

> [!NOTE]
> For backwards compatibility, if only a single input parameter is named `_`, then, within a lambda expression, `_` is treated as the name of that parameter.
> For backwards compatibility, if only a single input parameter is named `_`, `_` is treated as the name of that parameter within that lambda expression.

Beginning with C# 12, you can provide *default values* for parameters on lambda expressions. The syntax and the restrictions on default parameter values are the same as for methods and local functions. The following example declares a lambda expression with a default parameter, then calls it once using the default and once with two explicit parameters:
Beginning with C# 12, you can provide *default values* for explicitly typed parameter lists. The syntax and the restrictions on default parameter values are the same as for methods and local functions. The following example declares a lambda expression with a default parameter, then calls it once using the default and once with two explicit parameters:

:::code language="csharp" source="snippets/lambda-expressions/GeneralExamples.cs" id="SnippetDefaultParameters":::

You can also declare lambda expressions with `params` arrays or collections as parameters:
You can also declare lambda expressions with `params` arrays or collections as the last parameter in an explicitly typed parameter list:

:::code language="csharp" source="snippets/lambda-expressions/GeneralExamples.cs" id="SnippetParamsArray":::

Expand Down Expand Up @@ -166,7 +166,7 @@ For more information about how to create and use async methods, see [Asynchronou

## Lambda expressions and tuples

The C# language provides built-in support for [tuples](../builtin-types/value-tuples.md). You can provide a tuple as an argument to a lambda expression, and your lambda expression can also return a tuple. In some cases, the C# compiler uses type inference to determine the types of tuple components.
The C# language provides built-in support for [tuples](../builtin-types/value-tuples.md). You can provide a tuple as an argument to a lambda expression, and your lambda expression can also return a tuple. In some cases, the C# compiler uses type inference to determine the types of tuple elements.

You define a tuple by enclosing a comma-delimited list of its components in parentheses. The following example uses tuple with three components to pass a sequence of numbers to a lambda expression, which doubles each value and returns a tuple with three components that contains the result of the multiplications.

Expand Down Expand Up @@ -299,7 +299,7 @@ var inc = [return: NotNullIfNotNull(nameof(s))] (int? s) => s.HasValue ? s++ : n
As the preceding examples show, you must parenthesize the input parameters when you add attributes to a lambda expression or its parameters.

> [!IMPORTANT]
> Lambda expressions are invoked through the underlying delegate type. That is different than methods and local functions. The delegate's `Invoke` method doesn't check attributes on the lambda expression. Attributes don't have any effect when the lambda expression is invoked. Attributes on lambda expressions are useful for code analysis, and can be discovered via reflection. One consequence of this decision is that the <xref:System.Diagnostics.ConditionalAttribute?displayProperty=nameWithType> cannot be applied to a lambda expression.
> Lambda expressions are invoked through the underlying delegate type. That invocation is different than methods and local functions. The delegate's `Invoke` method doesn't check attributes on the lambda expression. Attributes don't have any effect when the lambda expression is invoked. Attributes on lambda expressions are useful for code analysis, and can be discovered via reflection. One consequence of this decision is that the <xref:System.Diagnostics.ConditionalAttribute?displayProperty=nameWithType> can't be applied to a lambda expression.
## Capture of outer variables and variable scope in lambda expressions

Expand All @@ -309,7 +309,7 @@ Lambdas can refer to *outer variables*. These *outer variables* are the variable

The following rules apply to variable scope in lambda expressions:

- A variable that is captured won't be garbage-collected until the delegate that references it becomes eligible for garbage collection.
- A variable that is captured isn't garbage-collected until the delegate that references it becomes eligible for garbage collection.
- Variables introduced within a lambda expression aren't visible in the enclosing method.
- A lambda expression can't directly capture an [in](../keywords/method-parameters.md#in-parameter-modifier), [ref](../keywords/ref.md), or [out](../keywords/method-parameters.md#out-parameter-modifier) parameter from the enclosing method.
- A [return](../statements/jump-statements.md#the-return-statement) statement in a lambda expression doesn't cause the enclosing method to return.
Expand Down
12 changes: 6 additions & 6 deletions docs/csharp/language-reference/operators/lambda-operator.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "The lambda operator - The `=>` operator is used to define a lambda expression"
description: "The C# => operator defines lambda expressions and expression bodied members. Lambda expressions define a block of code used as data."
ms.date: 11/28/2022
ms.date: 02/13/2025
f1_keywords:
- "=>_CSharpKeyword"
helpviewer_keywords:
Expand All @@ -19,15 +19,15 @@ In [lambda expressions](lambda-expressions.md), the lambda operator `=>` separat

The following example uses the [LINQ](/dotnet/csharp/linq/) feature with method syntax to demonstrate the usage of lambda expressions:

[!code-csharp-interactive[infer types of input variables](snippets/shared/LambdaOperator.cs#InferredTypes)]
:::code language="csharp" interactive="try-dotnet-method" source="snippets/shared/LambdaOperator.cs" id="InferredTypes":::

Input parameters of a lambda expression are strongly typed at compile time. When the compiler can infer the types of input parameters, like in the preceding example, you may omit type declarations. If you need to specify the type of input parameters, you must do that for each parameter, as the following example shows:
Input parameters of a lambda expression are strongly typed at compile time. When the compiler can infer the types of input parameters, like in the preceding example, you can omit type declarations. If you need to specify the type of input parameters, you must do that for each parameter, as the following example shows:

[!code-csharp-interactive[specify types of input variables](snippets/shared/LambdaOperator.cs#ExplicitTypes)]
:::code language="csharp" interactive="try-dotnet-method" source="snippets/shared/LambdaOperator.cs" id="ExplicitTypes":::

The following example shows how to define a lambda expression without input parameters:

[!code-csharp-interactive[without input variables](snippets/shared/LambdaOperator.cs#WithoutInput)]
:::code language="csharp" interactive="try-dotnet-method" source="snippets/shared/LambdaOperator.cs" id="WithoutInput":::

For more information, see [Lambda expressions](lambda-expressions.md).

Expand All @@ -39,7 +39,7 @@ An expression body definition has the following general syntax:
member => expression;
```

where `expression` is a valid expression. The return type of `expression` must be implicitly convertible to the member's return type. If the member:
Where `expression` is a valid expression. The return type of `expression` must be implicitly convertible to the member's return type. If the member:

- Has a `void` return type or
- Is a:
Expand Down
2 changes: 2 additions & 0 deletions docs/csharp/specification/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ items:
href: ../../../_csharplang/proposals/csharp-13.0/method-group-natural-type-improvements.md
- name: Optional Lambda expression parameters
href: ../../../_csharplang/proposals/csharp-12.0/lambda-method-group-defaults.md
- name: Simple lambda parameters with modifiers
href: ../../../_csharplang/proposals/lambda-parameters-with-modifiers.md
- name: Checked user-defined operators
href: ../../../_csharplang/proposals/csharp-11.0/checked-user-defined-operators.md
- name: Unsigned right-shift operator
Expand Down
20 changes: 20 additions & 0 deletions docs/csharp/whats-new/csharp-14.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,24 @@ You can find the list of implicit span conversions in the article on [built-in t

Beginning with C# 14, the argument to `nameof` can be an unbound generic type. For example, `nameof(List<>)` evaluates to `List`. In earlier versions of C#, only closed generic types, such as `List<int>`, could be used to produce `List`.

## Simple lambda parameters with modifiers

Beginning with C# 14, you can add parameter modifiers, such as `scoped`, `ref`, `in`, `out`, or `ref readonly` to lambda expression parameters without specifying the parameter type:

```csharp
delegate bool TryParse<T>(string text, out T result);
// ...
TryParse<int> parse1 = (text, out result) => Int32.TryParse(text, out result);
```

Previously, adding any modifiers was allowed only when the parameter declarations included the types for the parameters. The preceding declaration would require typs on all parameters:

```csharp
TryParse<int> parse2 = (string text, out int result) => Int32.TryParse(text, out result);
```

The `params` modifier still requires an explicitly typed parameter list.

You can read more about these changes in the article on [lambda expressions](../language-reference/operators/lambda-expressions.md#input-parameters-of-a-lambda-expression) in the C# language reference.

<!-- Add link to What's new in .NET 10 once it's published -->

0 comments on commit 854ccdc

Please sign in to comment.