Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix 13323 #22847

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion docs/csharp/language-reference/operators/lambda-expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,20 @@ You can use lambda expressions in any code that requires instances of delegate t

[!code-csharp-interactive[lambda is argument in LINQ](snippets/lambda-expressions/Introduction.cs#Argument)]

When you use method-based syntax to call the <xref:System.Linq.Enumerable.Select%2A?displayProperty=nameWithType> method in the <xref:System.Linq.Enumerable?displayProperty=nameWithType> class, for example in LINQ to Objects and LINQ to XML, the parameter is a delegate type <xref:System.Func%602?displayProperty=nameWithType>. When you call the <xref:System.Linq.Queryable.Select%2A?displayProperty=nameWithType> method in the <xref:System.Linq.Queryable?displayProperty=nameWithType> class, for example in LINQ to SQL, the parameter type is an expression tree type [`Expression<Func<TSource,TResult>>`](<xref:System.Linq.Expressions.Expression%601>). In both cases you can use the same lambda expression to specify the parameter value. That makes the two `Select` calls to look similar although in fact the type of objects created from the lambdas is different.
When you use method-based syntax to call the <xref:System.Linq.Enumerable.Select%2A?displayProperty=nameWithType> method in the <xref:System.Linq.Enumerable?displayProperty=nameWithType> class, for example in LINQ to Objects and LINQ to XML, the parameter is a delegate type <xref:System.Func%602?displayProperty=nameWithType>. When you call the <xref:System.Linq.Queryable.Select%2A?displayProperty=nameWithType> method in the <xref:System.Linq.Queryable?displayProperty=nameWithType> class, for example in LINQ to SQL, the parameter type is an expression tree type [`Expression<Func<TSource,TResult>>`](<xref:System.Linq.Expressions.Expression%601>).While in both cases you can use the same lambda expression to specify the parameter value, the target types are different, and thus the objects created from the lambda expression is different.

When you target a delegate type with a lambda expression, you will get back an invokable delegate instance, as in the first example. But when you target an expression tree type with a lambda expression, you'll get back an object describing the code operations&mdash;e.g. "multiply the parameter `x` by itself", or "get the `Foo` property's value from parameter `y`". This expression tree object cannot be invoked:

[!code-csharp-interactive[lambda is argument in LINQ](snippets/lambda-expressions/Introduction.cs#NonInvokableExpressionTree)]

But you can inspect the expression tree:

[!code-csharp-interactive[lambda is argument in LINQ](snippets/lambda-expressions/Introduction.cs#InspectExpressionTree)]

and pass it to other code&mdash;such as a LINQ provider&mdash;which may perform further actions or return some result.

> [!NOTE]
> Some code operations aren't supported by a given expression tree consumer&mdash;e.g. `.ToString` in the Entity Framework LINQ provider. Consult the relevant documentation to find out which operations are supported.

## Expression lambdas

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,28 @@ private static void LambdaArgument()
// 4 9 16 25
// </SnippetArgument>
}

private static void NonInvokableExpressionTree()
{
// <SnippetNonInvokableExpressionTree>
Expression<Func<int, int>> expr = (int x) => x * x;

// The following will not compile:
// expr(3);
// </SnippetNonInvokableExpressionTree>
}

private static void InspectExpressionTree()
{
// <SnippetInspectExpressionTree>
Func<int, int> fn = (int x) => x * x;
Console.WriteLine(expr.Parameters[0].Name);
// Output:
// x
Console.WriteLine(expr.Body.NodeType);
// Output:
// Multiply
// </SnippetInspectExpressionTree>
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The following sections describe specific techniques for querying differently in
- Call additional LINQ methods
- Vary the expression tree passed into the LINQ methods
- Construct an [Expression\<TDelegate>](xref:System.Linq.Expressions.Expression%601) expression tree using the factory methods at <xref:System.Linq.Expressions.Expression>
- Adding method call nodes to an <xref:System.Linq.IQueryable>'s expression tree
- Add method call nodes to an <xref:System.Linq.IQueryable>'s expression tree
- Construct strings, and use the [Dynamic LINQ library](https://dynamic-linq.net/)

## Use runtime state from within the expression tree
Expand All @@ -47,7 +47,7 @@ Generally, the [built-in LINQ methods](https://github.com/dotnet/runtime/blob/ma
* Wrap the current expression tree in a <xref:System.Linq.Expressions.MethodCallExpression> representing the method call.
* Pass the wrapped expression tree back to the provider, either to return a value via the provider's <xref:System.Linq.IQueryProvider.Execute%2A?displayProperty=nameWithType> method; or to return a translated query object via the <xref:System.Linq.IQueryProvider.CreateQuery%2A?displayProperty=nameWithType> method.

You can replace the original query with the result of an [IQueryable\<T>](xref:System.Linq.IQueryable%601)-returning method, to get a new query. You can do this based on runtime state, as in the following example:
You can replace the original query with the result of an [IQueryable\<T>](xref:System.Linq.IQueryable%601)-returning method, to get a new query. You can do this conditionally based on runtime state, as in the following example:

:::code language="csharp" source="../../../../../samples/snippets/csharp/programming-guide/dynamic-linq-expression-trees/Program.cs" id="Added_method_calls":::

Expand All @@ -63,17 +63,15 @@ You might also want to compose the various sub-expressions using a third-party l

## Construct expression trees and queries using factory methods

In all the examples up to this point, we've known the element type at compile time&mdash;`string`&mdash;and thus the type of the query&mdash;`IQueryable<string>`. You may need to add components to a query of any element type. You may need to add different components, depending on the element type. You can create expression trees from the ground up, using the factory methods at <xref:System.Linq.Expressions.Expression?displayProperty=fullName>, and thus tailor the expression to a specific element type.
In all the examples up to this point, we've known the element type at compile time&mdash;`string`&mdash;and thus the type of the query&mdash;`IQueryable<string>`. You may need to add components to a query of any element type, or to add different components, depending on the element type. You can create expression trees from the ground up, using the factory methods at <xref:System.Linq.Expressions.Expression?displayProperty=fullName>, and thus tailor the expression at runtime to a specific element type.

### Constructing an [Expression\<TDelegate>](xref:System.Linq.Expressions.Expression%601)

When you construct an expression to pass into one of the LINQ methods, you're actually constructing an instance of [Expression\<TDelegate>](xref:System.Linq.Expressions.Expression%601), where `TDelegate` is some delegate type such as `Func<string, bool>`, `Action`, or a custom delegate type.

[Expression\<TDelegate>](xref:System.Linq.Expressions.Expression%601) inherits from <xref:System.Linq.Expressions.LambdaExpression>, which represents a complete lambda expression like the following:

```csharp
Expression<Func<string, bool>> expr = x => x.StartsWith("a");
```
:::code language="csharp" source="../../../../../samples/snippets/csharp/programming-guide/dynamic-linq-expression-trees/Program.cs" id="Compiler_generated_expression_tree":::

A <xref:System.Linq.Expressions.LambdaExpression> has two components:

Expand All @@ -84,36 +82,23 @@ The basic steps in constructing an [Expression\<TDelegate>](xref:System.Linq.Exp

* Define <xref:System.Linq.Expressions.ParameterExpression> objects for each of the parameters (if any) in the lambda expression, using the <xref:System.Linq.Expressions.Expression.Parameter%2A> factory method.

```csharp
ParameterExpression x = Parameter(typeof(string), "x");
```
:::code language="csharp" source="../../../../../samples/snippets/csharp/programming-guide/dynamic-linq-expression-trees/Program.cs" id="Factory_method_expression_tree_parameter":::

* Construct the body of your <xref:System.Linq.Expressions.LambdaExpression>, using the <xref:System.Linq.Expressions.ParameterExpression> you've defined. For instance, an expression representing `x.StartsWith("a")` could be constructed like this:
* Construct the body of your <xref:System.Linq.Expressions.LambdaExpression>, using the <xref:System.Linq.Expressions.ParameterExpression>(s) you've defined, and the factory methods at <xref:System.Linq.Expressions.Expression>. For instance, an expression representing `x.StartsWith("a")` could be constructed like this:

```csharp
Expression body = Call(
x,
typeof(string).GetMethod("StartsWith", new [] {typeof(string)}),
Constant("a")
);
```
:::code language="csharp" source="../../../../../samples/snippets/csharp/programming-guide/dynamic-linq-expression-trees/Program.cs" id="Factory_method_expression_tree_body":::

* Wrap the parameters and body in a compile-time-typed [Expression\<TDelegate>](xref:System.Linq.Expressions.Expression%601), using the appropriate <xref:System.Linq.Expressions.Expression.Lambda%2A> factory method overload:

```csharp
Expression<Func<string, bool>> expr = Lambda<Func<string, bool>>(body, prm);
```
:::code language="csharp" source="../../../../../samples/snippets/csharp/programming-guide/dynamic-linq-expression-trees/Program.cs" id="Factory_method_expression_tree_lambda":::

The following sections describe a scenario in which you might want to construct an [Expression\<TDelegate>](xref:System.Linq.Expressions.Expression%601) to pass into a LINQ method, and provide a complete example of how to do so using the factory methods.

### Scenario

Let's say you have multiple entity types:

```csharp
record Person(string LastName, string FirstName, DateTime DateOfBirth);
record Car(string Model, int Year);
```
:::code language="csharp" source="../../../../../samples/snippets/csharp/programming-guide/dynamic-linq-expression-trees/Program.cs" id="Entities":::

For any of these entity types, you want to filter and return only those entities that have a given text inside one of their `string` fields. For `Person`, you'd want to search the `FirstName` and `LastName` properties:

Expand Down Expand Up @@ -143,7 +128,7 @@ Because the `TextFilter` function takes and returns an [IQueryable\<T>](xref:Sys

:::code language="csharp" source="../../../../../samples/snippets/csharp/programming-guide/dynamic-linq-expression-trees/Program.cs" id="Factory_methods_expression_of_tdelegate_usage":::

## Adding method call nodes to the <xref:System.Linq.IQueryable>'s expression tree
## Add method call nodes to the <xref:System.Linq.IQueryable>'s expression tree

If you have an <xref:System.Linq.IQueryable> instead of an [IQueryable\<T>](xref:System.Linq.IQueryable%601), you can't directly call the generic LINQ methods. One alternative is to build the inner expression tree as above, and use reflection to invoke the appropriate LINQ method while passing in the expression tree.

Expand Down
Loading