ZetCode

C# LINQ let

last modified May 14, 2025

This article explores how to use the LINQ let clause to introduce intermediate variables within query expressions, improving readability, efficiency, and maintainability.

The let clause allows developers to define temporary variables within a LINQ query. These variables store computed values, reducing redundant calculations and enhancing the clarity of complex queries. Instead of repeating expressions multiple times, you can assign results to a let variable and reference it throughout the query.

Benefits of using let:

The let clause is commonly used in LINQ queries to store intermediate results such as calculated values, transformed data, or formatted output. It can simplify filtering conditions, aid in grouping operations, and improve the overall clarity of data processing tasks.

While the let clause enhances query efficiency, it should be used judiciously. Variables introduced via let are computed once per iteration, meaning they are not stored persistently but recalculated for each item in the collection. This makes it ideal for lightweight computations but may not be suitable for scenarios requiring frequent value modification.

C# LINQ let basic example

The simplest use of let creates a temporary variable to avoid repeating calculations.

Program.cs
string[] words = ["apple", "banana", "cherry", "date", "elderberry"];

var query = from word in words
            let length = word.Length
            where length > 5
            select new { Word = word, Length = length };

foreach (var item in query)
{
    Console.WriteLine($"{item.Word} - {item.Length} letters");
}

We calculate word lengths once and reuse the value in both the where clause and the final projection.

let length = word.Length

The let clause creates a temporary variable length that can be used throughout the rest of the query.

$ dotnet run 
banana - 6 letters
cherry - 6 letters
elderberry - 10 letters

C# LINQ let with complex expressions

let can store the results of more complex expressions for reuse.

Program.cs
List<Product> products = 
[
    new("Laptop", 999.99m, 5),
    new("Phone", 699.99m, 10),
    new("Tablet", 349.99m, 3),
    new("Monitor", 249.99m, 7)
];

var query = from p in products
            let totalValue = p.Price * p.Stock
            where totalValue > 2000
            orderby totalValue descending
            select new { p.Name, TotalValue = totalValue.ToString("C") };

foreach (var product in query)
{
    Console.WriteLine($"{product.Name}: {product.TotalValue}");
}

record Product(string Name, decimal Price, int Stock);

We calculate the total inventory value for each product and use it in multiple clauses.

let totalValue = p.Price * p.Stock

The intermediate calculation is performed once but used in both the filtering and sorting operations.

$ dotnet run 
Phone: $6,999.90
Laptop: $4,999.95
Monitor: $1,749.93

C# LINQ let with string manipulation

let is particularly useful when working with string operations.

Program.cs
List<string> names = 
[
    "John Smith",
    "Alice Johnson",
    "Robert Brown",
    "Emily Davis",
    "Michael Wilson"
];

var query = from name in names
            let parts = name.Split(' ')
            let firstName = parts[0]
            let lastName = parts[1]
            let initials = $"{firstName[0]}{lastName[0]}"
            select new { FullName = name, Initials = initials };

foreach (var person in query)
{
    Console.WriteLine($"{person.Initials}: {person.FullName}");
}

We break down names into components and create initials without repeating string operations.

let parts = name.Split(' ')
let firstName = parts[0]
let lastName = parts[1]
let initials = $"{firstName[0]}{lastName[0]}"

Multiple let clauses create a pipeline of transformations, each building on the previous ones.

$ dotnet run 
JS: John Smith
AJ: Alice Johnson
RB: Robert Brown
ED: Emily Davis
MW: Michael Wilson

C# LINQ let with method calls

let can store the results of method calls to avoid repeated execution.

Program.cs
List<DateTime> dates = 
[
    new DateTime(2023, 1, 15),
    new DateTime(2023, 3, 22),
    new DateTime(2023, 6, 8),
    new DateTime(2023, 9, 30),
    new DateTime(2023, 12, 5)
];


var query = from date in dates
            let quarter = GetQuarter(date)
            where quarter > 2
            group date by quarter into dateGroup
            select new { Quarter = dateGroup.Key, Dates = dateGroup };

foreach (var group in query)
{
    Console.WriteLine($"Quarter {group.Quarter}:");
    foreach (var date in group.Dates)
    {
        Console.WriteLine($"  {date:d}");
    }
}

static int GetQuarter(DateTime date) => (date.Month - 1) / 3 + 1;

We calculate the quarter for each date once and use it for both filtering and grouping.

let quarter = GetQuarter(date)

The method result is cached in the quarter variable, ensuring the calculation happens only once per element.

$ dotnet run 
Quarter 3:
  30. 9. 2023
Quarter 4:
  5. 12. 2023

C# LINQ let with anonymous types

You can use let to create anonymous types with multiple computed properties in a query.

Program.cs
string[] words = ["mountain", "river", "forest", "valley", "desert"];

var query = from word in words
            let upper = word.ToUpper()
            let reversed = new string([.. word.Reverse()])
            select new { Original = word, Upper = upper, Reversed = reversed };

foreach (var item in query)
{
    Console.WriteLine($"{item.Original} | {item.Upper} | {item.Reversed}");
}

This example uses let to store both the uppercase and reversed versions of each word, then projects them into an anonymous type.

C# LINQ let with nested collections

The let clause can help flatten and filter nested collections, such as students and their grades.

Program.cs
var students = new[]
{
    new { Name = "Anna", Grades = new[] { 90, 85, 92 } },
    new { Name = "Ben", Grades = new[] { 78, 81, 86 } },
    new { Name = "Cara", Grades = new[] { 88, 94, 91 } }
};

var query = from student in students
            let highGrades = student.Grades.Where(g => g >= 90)
            where highGrades.Any()
            select new { student.Name, HighGrades = highGrades };

foreach (var s in query)
{
    Console.WriteLine($"{s.Name}: {string.Join(", ", s.HighGrades)}");
}

Here, let is used to extract high grades for each student, and only students with at least one high grade are included in the result.

C# LINQ let vs multiple from clauses

let differs from multiple from clauses in how it affects the query structure.

Program.cs
List<List<int>> numberLists = [
[
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

// Using multiple from clauses (cross join)
var query1 = from list in numberLists
             from number in list
             where number % 2 == 0
             select number;

// Using let to reference the inner list
var query2 = from list in numberLists
             let evenNumbers = list.Where(n => n % 2 == 0)
             where evenNumbers.Any()
             select new { List = list, Evens = evenNumbers };

Console.WriteLine("Multiple from clauses (flattened):");
foreach (var num in query1) Console.WriteLine(num);

Console.WriteLine("\nUsing let (structured):");
foreach (var item in query2)
{
    Console.WriteLine($"List: [{string.Join(", ", item.List)}]");
    Console.WriteLine($"Even numbers: [{string.Join(", ", item.Evens)}]");
}

let preserves the original structure while multiple from clauses flatten the results.

let evenNumbers = list.Where(n => n % 2 == 0)

The let clause maintains the relationship between each list and its even numbers.

$ dotnet run 
Multiple from clauses (flattened):
2
4
6
8

Using let (structured):
List: [1, 2, 3]
Even numbers: [2]
List: [4, 5, 6]
Even numbers: [4, 6]
List: [7, 8, 9]
Even numbers: [8]

Source

let clause (C# Reference)

In this article we showed how to use the LINQ let clause to create intermediate variables that improve query readability and performance.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all C# tutorials.