Skip to content
Draft
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
14 changes: 13 additions & 1 deletion .openpublishing.redirection.csharp.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
},
{
"source_path_from_root": "/docs/csharp/deconstruct.md",
"redirect_url": "/dotnet/csharp/fundamentals/patterns/deconstruct"

Check failure on line 37 in .openpublishing.redirection.csharp.json

View workflow job for this annotation

GitHub Actions / MSDocs build verifier

Redirect target returns 404: '/dotnet/csharp/fundamentals/patterns/deconstruct'.
},
{
"source_path_from_root": "/docs/csharp/delegates-events.md",
Expand Down Expand Up @@ -82,7 +82,7 @@
},
{
"source_path_from_root": "/docs/csharp/fundamentals/functional/deconstruct.md",
"redirect_url": "/dotnet/csharp/fundamentals/patterns/deconstruct"

Check failure on line 85 in .openpublishing.redirection.csharp.json

View workflow job for this annotation

GitHub Actions / MSDocs build verifier

Redirect target returns 404: '/dotnet/csharp/fundamentals/patterns/deconstruct'.
},
{
"source_path_from_root": "/docs/csharp/fundamentals/functional/discards.md",
Expand All @@ -102,7 +102,7 @@
},
{
"source_path_from_root": "/docs/csharp/fundamentals/tutorials/pattern-matching.md",
"redirect_url": "/dotnet/csharp/fundamentals/tutorials/pattern-matching-basics"

Check failure on line 105 in .openpublishing.redirection.csharp.json

View workflow job for this annotation

GitHub Actions / MSDocs build verifier

Redirect target returns 404: '/dotnet/csharp/fundamentals/tutorials/pattern-matching-basics'.
},
{
"source_path_from_root": "/docs/csharp/fundamentals/types/anonymous-types.md",
Expand Down Expand Up @@ -242,6 +242,10 @@
"source_path_from_root": "/docs/csharp/interop.md",
"redirect_url": "/dotnet/csharp/advanced-topics/interop/index"
},
{
"source_path_from_root": "/docs/csharp/iterators.md",
"redirect_url": "/dotnet/csharp/fundamentals/functional/iterators"
},
{
"source_path_from_root": "/docs/csharp/lambda-expressions.md",
"redirect_url": "/dotnet/csharp/language-reference/operators/lambda-expressions"
Expand Down Expand Up @@ -1822,7 +1826,7 @@
},
{
"source_path_from_root": "/docs/csharp/local-functions-vs-lambdas.md",
"redirect_url": "/dotnet/csharp/programming-guide/classes-and-structs/local-functions"
"redirect_url": "/dotnet/csharp/fundamentals/functional/local-functions"
},
{
"source_path_from_root": "/docs/csharp/methods-lambda-expressions.md",
Expand Down Expand Up @@ -3472,6 +3476,10 @@
"source_path_from_root": "/docs/csharp/programming-guide/classes-and-structs/inheritance.md",
"redirect_url": "/dotnet/csharp/fundamentals/object-oriented/inheritance"
},
{
"source_path_from_root": "/docs/csharp/programming-guide/classes-and-structs/local-functions.md",
"redirect_url": "/dotnet/csharp/fundamentals/functional/local-functions"
},
{
"source_path_from_root": "/docs/csharp/programming-guide/classes-and-structs/objects.md",
"redirect_url": "/dotnet/csharp/fundamentals/object-oriented/objects"
Expand Down Expand Up @@ -3667,6 +3675,10 @@
"source_path_from_root": "/docs/csharp/programming-guide/concepts/expression-trees/index.md",
"redirect_url": "/dotnet/csharp/advanced-topics/expression-trees/index"
},
{
"source_path_from_root": "/docs/csharp/programming-guide/concepts/iterators.md",
"redirect_url": "/dotnet/csharp/fundamentals/functional/iterators"
},
{
"source_path_from_root": "/docs/csharp/programming-guide/concepts/linq/adding-elements-attributes-and-nodes-to-an-xml-tree.md",
"redirect_url": "/dotnet/standard/linq/add-elements-attributes-nodes-xml-tree",
Expand Down Expand Up @@ -5548,7 +5560,7 @@
},
{
"source_path_from_root": "/docs/csharp/tutorials/exploration/patterns-objects.md",
"redirect_url": "/dotnet/csharp/fundamentals/tutorials/pattern-matching-basics"

Check failure on line 5563 in .openpublishing.redirection.csharp.json

View workflow job for this annotation

GitHub Actions / MSDocs build verifier

Redirect target returns 404: '/dotnet/csharp/fundamentals/tutorials/pattern-matching-basics'.
},
{
"source_path_from_root": "/docs/csharp/tutorials/exploration/records.md",
Expand Down Expand Up @@ -5636,11 +5648,11 @@
},
{
"source_path_from_root": "/docs/csharp/tutorials/pattern-matching.md",
"redirect_url": "/dotnet/csharp/fundamentals/tutorials/build-algorithms-using-pattern-matching"

Check failure on line 5651 in .openpublishing.redirection.csharp.json

View workflow job for this annotation

GitHub Actions / MSDocs build verifier

Redirect target returns 404: '/dotnet/csharp/fundamentals/tutorials/build-algorithms-using-pattern-matching'.
},
{
"source_path_from_root": "/docs/csharp/tutorials/patterns-objects.md",
"redirect_url": "/dotnet/csharp/fundamentals/tutorials/pattern-matching-basics"

Check failure on line 5655 in .openpublishing.redirection.csharp.json

View workflow job for this annotation

GitHub Actions / MSDocs build verifier

Redirect target returns 404: '/dotnet/csharp/fundamentals/tutorials/pattern-matching-basics'.
},
{
"source_path_from_root": "/docs/csharp/tutorials/records.md",
Expand Down Expand Up @@ -5731,7 +5743,7 @@
},
{
"source_path_from_root": "/docs/csharp/whats-new/tutorials/patterns-objects.md",
"redirect_url": "/dotnet/csharp/fundamentals/tutorials/pattern-matching-basics"

Check failure on line 5746 in .openpublishing.redirection.csharp.json

View workflow job for this annotation

GitHub Actions / MSDocs build verifier

Redirect target returns 404: '/dotnet/csharp/fundamentals/tutorials/pattern-matching-basics'.
},
{
"source_path_from_root": "/docs/csharp/whats-new/tutorials/ranges-indexes.md",
Expand Down
2 changes: 1 addition & 1 deletion docs/csharp/advanced-topics/expression-trees/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Expression trees don't support new expression node types. It would be a breaking
- [Conditional methods](../../language-reference/preprocessor-directives.md#conditional-compilation) removed from the output
- [`base` access](../../language-reference/keywords/base.md)
- Method group expressions, including [*address-of* (`&`)](../../language-reference/operators/pointer-related-operators.md) a method group, and anonymous method expressions
- References to [local functions](../../programming-guide/classes-and-structs/local-functions.md)
- References to [local functions](../../fundamentals/functional/local-functions.md)
- Statements, including assignment (`=`) and statement bodied expressions
- [Partial methods](../../language-reference/keywords/partial-member.md) with only a defining declaration
- [Unsafe pointer operations](../../language-reference/unsafe-code.md#pointer-types)
Expand Down
67 changes: 67 additions & 0 deletions docs/csharp/fundamentals/functional/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
title: "Functional techniques overview"
description: Learn how lambdas, local functions, and iterators help you pass behavior, organize helper logic, and produce sequences in C#.
ms.date: 10/06/2026
ms.topic: overview
ai-usage: ai-generated
---

# Functional techniques overview

> [!TIP]
> This article is part of the **Fundamentals** section for developers who know basic C# expressions, statements, and methods. If you're new to programming, start with the [Get started](../../tour-of-csharp/tutorials/index.md) tutorials first.

C# is a *multi-paradigm language*. You can combine ideas from several programming styles instead of following one style throughout an application. Object-oriented code organizes data and behavior in types. Functional techniques treat behavior as a value, keep helper logic close to its use, or describe a sequence of values.

This section introduces three techniques that solve different problems:

- A [lambda expression](lambdas.md) defines behavior that you can pass to another method or store in a variable.
- A [local function](local-functions.md) gives nearby helper logic a name and limits its use to the containing member.
- An [iterator](iterators.md) produces a sequence one element at a time for `foreach` or another sequence operation.

These techniques work together, but none replaces the others. Choose the technique that best communicates what the code does.

## Choose a technique

Start with the role that the code needs to play:

| Need | Choose | Why |
| --- | --- | --- |
| Pass a short operation to another method | Lambda expression | The behavior appears beside the call that uses it. |
| Pass an existing named method | Method group | The method name already explains the behavior, so you don't need a wrapper lambda. |
| Reuse named helper logic only inside one member | Local function | The helper stays near its caller and can't be called from elsewhere. |
| Produce values as a caller requests them | Iterator | The method exposes an `IEnumerable<T>` sequence without building the complete result first. |

For longer behavior, prefer a descriptive name over a large inline lambda. Use a local function when only the containing member needs that name. Use a regular method when several members need the same operation.

## Combine the techniques

The following example prepares tasks for a project dashboard. A lambda selects urgent tasks, a local function formats each task, and an iterator produces only tasks that are ready to start. A `ProjectTask` represents one item of project work.

:::code language="csharp" source="snippets/overview/Program.cs" id="CombineTechniques":::

Each technique has one clear job:

- `task => task.Priority >= minimumPriority` passes the selection rule to `Where`. The lambda captures `minimumPriority` from the surrounding method.
- `FormatTask` names formatting logic used only by `ShowDashboard`. The local function captures `projectName`.
- `ReadyTasks` uses `yield return` to provide ready tasks when the `foreach` loop requests them.

The example still uses familiar object-oriented types: `ProjectTask` groups related data, and .NET collection types store that data. Functional techniques complement other C# styles rather than require a different application design.

## Keep data transformations readable

Functional techniques often appear in a flow where one operation produces input for the next. Keep each step focused:

- Use names that describe intent, such as `ReadyTasks` or `FormatTask`.
- Keep a lambda short enough to understand at the call site.
- Move multi-step logic into a local function or regular method.
- Use an iterator when callers benefit from consuming elements as they're produced.

Pattern matching solves a different problem: it tests the type, value, or shape of data. Combine these techniques when they clarify separate parts of a task. For guidance about data tests, see [Pattern matching overview](../patterns/pattern-matching.md).

## Next steps

- [Pass behavior with lambda expressions](lambdas.md)
- [Organize helper logic with local functions](local-functions.md)
- [Produce sequences with iterators](iterators.md)
- [Lambda expressions, delegates, and events](../types/delegates-lambdas.md)
91 changes: 91 additions & 0 deletions docs/csharp/fundamentals/functional/iterators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
title: "Iterators"
description: Learn how C# iterator methods produce IEnumerable sequences with yield return and yield break for use with foreach.
ms.date: 10/06/2026
ms.topic: concept-article
ai-usage: ai-generated
---

# Iterators

> [!TIP]
> This article teaches synchronous iterators for developers who know methods, loops, and collections. For complete `yield` syntax and restrictions, see the [`yield` statement reference](../../language-reference/statements/yield.md).

An *iterator method* produces a sequence of values one at a time. This article focuses on iterator methods that return <xref:System.Collections.Generic.IEnumerable`1>, whose values callers usually consume with a `foreach` statement.

Use an iterator when the method can describe how to produce a sequence without building the complete result in a collection first. Iterator methods work especially well for filters, generated values, and pipelines where each step handles one element at a time.

## Produce elements with `yield return`

Use `yield return` to provide the next element in the sequence. The following iterator selects acceptable temperature readings from a sensor. In this scenario, `-1` marks the end of the sensor data.

:::code language="csharp" source="snippets/iterators/Program.cs" id="IteratorMethod":::

`SelectTemperatures` returns `IEnumerable<int>`, which represents a sequence of `int` values. Each `yield return reading` provides one value to the `foreach` loop. After the caller handles that value, the iterator continues with the next input.

Use the generic `IEnumerable<T>` form for most modern C# code. The type argument, such as `int`, tells callers which element type the sequence contains.

## End the sequence with `yield break`

Use `yield break` to end an iterator before control reaches the end of the method. In the example, the sentinel value `-1` means that no more readings are available:

```csharp
if (reading == -1)
{
yield break;
}
```

The iterator also ends naturally when execution reaches the end of its body. Use `yield break` when a condition should stop element production immediately.

## Consume an iterator with `foreach`

An iterator exposes the same `IEnumerable<T>` shape as many .NET collections. Callers can use `foreach` without knowing how the sequence produces its elements:

```csharp
foreach (int temperature in SelectTemperatures(readings, 25))
{
Console.WriteLine($"Accepted: {temperature}°C");
}
```

The caller requests each element as the loop advances. The iterator keeps its position between requests and continues after the previous `yield return`.

## Understand deferred element production

Calling an iterator method doesn't run its body immediately. The method starts to produce elements when the caller begins enumeration. This behavior is called *deferred execution*.

The example prints `Sequence created.` before any `Checking ...` messages. The iterator starts checking readings only when the `foreach` loop asks for its first element.

Deferred execution has practical effects:

- The caller can begin processing before the iterator produces every element.
- The iterator can stop early with `yield break`.
- A caller that never enumerates the sequence doesn't run the iterator body.
- Each new enumeration runs the iterator again.

Keep deferred execution visible in the method's purpose and naming. Avoid unexpected side effects in an iterator because callers might enumerate the sequence later or more than once.

## Choose an iterator or collection

Return an iterator when values naturally arrive one at a time or when the caller doesn't need the complete result immediately. Return a completed collection when callers need a stable snapshot, indexed access, or repeated enumeration without recalculating the elements.

Iterator methods compose with LINQ because both use `IEnumerable<T>`. For example, a caller can add another filter:

```csharp
var comfortableReadings = SelectTemperatures(readings, 25)
.Where(static temperature => temperature >= 18);
```

The iterator and LINQ operation remain deferred until code enumerates `comfortableReadings`.

For asynchronous sequences, see [Generate and consume asynchronous streams](../../asynchronous-programming/generate-consume-asynchronous-stream.md). For custom enumeration patterns and all `yield` restrictions, use the [language reference](../../language-reference/statements/yield.md).

## See also

- [Functional techniques overview](index.md)
- [Lambda expressions](lambdas.md)
- [Local functions](local-functions.md)
- [`foreach` statement](../../language-reference/statements/iteration-statements.md#the-foreach-statement)
- [`yield` statement](../../language-reference/statements/yield.md)
- [LINQ](../statements/linq.md)
120 changes: 120 additions & 0 deletions docs/csharp/fundamentals/functional/lambdas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
---
title: "Lambda expressions"
description: Learn how to pass behavior with C# lambda expressions, write parameter and body forms, capture variables, and choose between lambdas, method groups, and local functions.
ms.date: 10/06/2026
ms.topic: concept-article
ai-usage: ai-generated
---

# Lambda expressions

> [!TIP]
> This article teaches practical choices for developers who know C# methods and collections. For the complete lambda syntax and rules, see the [lambda expressions reference](../../language-reference/operators/lambda-expressions.md).

A *lambda expression* is an unnamed function that you write where another piece of code needs behavior. For example, a collection method might need a rule that selects items or a calculation that transforms each item. Pass that rule as a lambda instead of creating a named method for one short use.

The following lambda accepts a session and returns whether the session has open seats:

```csharp
session => session.Registered < session.Capacity
```

The `=>` operator separates the parameters on the left from the body on the right. Read it as "goes to."

## Pass behavior to a method

Many .NET APIs accept a delegate, which is a type that represents behavior with a particular parameter list and return type. A lambda provides that behavior. For an introduction to delegate types such as `Func` and `Action`, see [Lambda expressions, delegates, and events](../types/delegates-lambdas.md).

The following example works with workshop sessions. A `WorkshopSession` contains a title, duration, capacity, and registration count.

:::code language="csharp" source="snippets/lambdas/Program.cs" id="PassBehavior":::

The first lambda tells `Where` which sessions to keep. The second tells `OrderBy` which value to sort by. The methods receive behavior without knowing the details of either rule.

## Choose a parameter form

Choose the shortest parameter form that stays clear:

```csharp
session => session.DurationMinutes <= 60
```

For one parameter, omit parentheses when the compiler can infer the type. Use empty parentheses for no parameters:

```csharp
() => DateTime.Now
```

Use parentheses for two or more parameters:

```csharp
(left, right) => left + right
```

Usually, the method that receives the lambda supplies enough information for the compiler to infer parameter types. Write explicit types when inference needs help or when the types improve clarity:

```csharp
(WorkshopSession session) => session.Title
```

The [lambda expressions reference](../../language-reference/operators/lambda-expressions.md#input-parameters-of-a-lambda-expression) covers parameter modifiers, attributes, default values, and other specialized forms.

## Choose an expression or statement body

Use an *expression lambda* when one expression calculates the result:

```csharp
session => session.Registered < session.Capacity
```

Use a *statement lambda* when the operation needs more than one statement. Enclose the body in braces, and include `return` when the lambda returns a value:

:::code language="csharp" source="snippets/lambdas/Program.cs" id="StatementBody":::

Keep statement lambdas brief. When the body needs several steps or the same logic appears more than once, give the behavior a descriptive name with a local function or regular method.

## Capture values from the surrounding scope

A lambda can use a variable declared in the surrounding method. This use is called *capture*. In the first example, the selection lambda captures `maximumMinutes`:

```csharp
int maximumMinutes = 60;
var shortSessions = sessions.Where(
session => session.DurationMinutes <= maximumMinutes);
```

Capture helps a short rule use local context. The lambda captures the variable itself, not a copy of its value. If the variable changes before the lambda runs, the lambda sees the current value.

Make the needed context easy to identify. If a lambda doesn't need surrounding state, add `static` to prevent accidental capture:

```csharp
var openSessions = sessions.Where(
static session => session.Registered < session.Capacity);
```

A static lambda can use its parameters, values it declares, constants, and static members. It can't use local variables or instance state from the surrounding code.

## Choose a lambda, method group, or local function

Several C# forms can supply behavior to a delegate parameter:

- Use a **lambda expression** for short behavior that reads clearly beside the call.
- Use a **method group** when an existing method has the required signature and its name explains the behavior.
- Use a **local function** when the containing member needs reusable named logic, recursion, or a body that's too large for an inline lambda.
- Use a **regular method** when multiple members need the behavior.

A *method group* is a method name without its argument list. The compiler converts the matching method to the required delegate type:

:::code language="csharp" source="snippets/lambdas/Program.cs" id="MethodGroup":::

`Where(HasOpenSeats)` communicates the same rule as `Where(session => HasOpenSeats(session))` with less code. Keep the lambda wrapper when it adds an argument, combines operations, or makes the call clearer.

For a detailed local comparison, see [Local functions](local-functions.md#choose-a-local-function-or-lambda-expression).

## See also

- [Functional techniques overview](index.md)
- [Local functions](local-functions.md)
- [Lambda expressions reference](../../language-reference/operators/lambda-expressions.md)
- [Lambda expressions, delegates, and events](../types/delegates-lambdas.md)
- [LINQ](../statements/linq.md)
Loading
Loading