C# LINQ let
最后修改于 2025 年 5 月 14 日
本文探讨如何使用 LINQ 的 let 子句在查询表达式中引入中间变量,从而提高可读性、效率和可维护性。
let 子句允许开发者在 LINQ 查询中定义临时变量。 这些变量存储计算值,减少冗余计算并提高复杂查询的清晰度。 无需多次重复表达式,您可以将结果分配给 let 变量并在整个查询中引用它。
使用 let 的好处
- 提高可读性: 通过将复杂表达式分解为更小、更易于管理的组件来简化它们。
- 增强性能: 减少冗余计算并提高查询执行效率。
- 更高的灵活性: 可以在查询中进行进一步的转换、过滤和分组。
let 子句通常用于 LINQ 查询中,以存储中间结果,例如计算值、转换后的数据或格式化的输出。 它可以简化过滤条件、帮助分组操作并提高数据处理任务的整体清晰度。
虽然 let 子句可以提高查询效率,但应谨慎使用。 通过 let 引入的变量在每次迭代中计算一次,这意味着它们不会被持久存储,而是为集合中的每个项目重新计算。 这使得它非常适合轻量级计算,但不适用于需要频繁修改值的情况。
C# LINQ let 基本示例
let 最简单的用法是创建一个临时变量来避免重复计算。
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");
}
我们计算一次单词长度,并在 where 子句和最终投影中重用该值。
let length = word.Length
let 子句创建一个临时变量 length,可以在查询的其余部分中使用。
$ dotnet run banana - 6 letters cherry - 6 letters elderberry - 10 letters
C# LINQ let 与复杂表达式
let 可以存储更复杂表达式的结果以供重用。
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);
我们计算每个产品的总库存价值,并在多个子句中使用它。
let totalValue = p.Price * p.Stock
中间计算执行一次,但在过滤和排序操作中都使用。
$ dotnet run Phone: $6,999.90 Laptop: $4,999.95 Monitor: $1,749.93
C# LINQ let 与字符串操作
在处理字符串操作时,let 特别有用。
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}");
}
我们将名称分解为组件并创建首字母缩写,而无需重复字符串操作。
let parts = name.Split(' ')
let firstName = parts[0]
let lastName = parts[1]
let initials = $"{firstName[0]}{lastName[0]}"
多个 let 子句创建一个转换管道,每个转换都建立在前一个转换的基础上。
$ dotnet run JS: John Smith AJ: Alice Johnson RB: Robert Brown ED: Emily Davis MW: Michael Wilson
C# LINQ let 与方法调用
let 可以存储方法调用的结果以避免重复执行。
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;
我们计算每个日期的季度一次,并将其用于过滤和分组。
let quarter = GetQuarter(date)
方法结果缓存在 quarter 变量中,确保每个元素只计算一次。
$ dotnet run Quarter 3: 30. 9. 2023 Quarter 4: 5. 12. 2023
C# LINQ let 与匿名类型
您可以使用 let 在查询中创建具有多个计算属性的匿名类型。
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}");
}
此示例使用 let 存储每个单词的大写版本和反转版本,然后将它们投影到匿名类型中。
C# LINQ let 与嵌套集合
let 子句可以帮助展平和过滤嵌套集合,例如学生及其成绩。
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)}");
}
在这里,let 用于提取每个学生的高分,并且只有至少有一个高分的学生才会包含在结果中。
C# LINQ let vs 多个 from 子句
let 与多个 from 子句的不同之处在于它如何影响查询结构。
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 保留原始结构,而多个 from 子句展平结果。
let evenNumbers = list.Where(n => n % 2 == 0)
let 子句维护每个列表与其偶数之间的关系。
$ 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]
来源
在本文中,我们展示了如何使用 LINQ let 子句来创建中间变量,从而提高查询的可读性和性能。
作者
列出所有 C# 教程。