C# LINQ
最后修改于 2024 年 7 月 7 日
在本文中,我们将展示如何在 C# 中使用语言集成查询 (LINQ)。
语言集成查询 (Language-Integrated Query, LINQ) 是一系列技术的名称,其基础是将查询功能直接集成到 C# 语言中。LINQ 为对象 (LINQ to Objects)、关系数据库 (LINQ to SQL) 和 XML (LINQ to XML) 提供了一致的查询体验。
LINQ 通过添加类似于 SQL 语句的查询表达式来扩展该语言。LINQ 查询表达式可用于方便地从数组、可枚举类、XML 文档、关系数据库和第三方数据源中提取和处理数据。
查询表达式可用于查询和转换来自任何支持 LINQ 的数据源的数据。查询表达式具有延迟执行的特性。它们直到我们迭代查询变量时(例如,在 foreach 语句中)才会被执行。
LINQ 查询可以用查询语法或方法语法编写。
LINQ 扩展方法位于 System.Linq 命名空间中。
C# LINQ 查询语法和方法语法
在 LINQ 中,我们可以使用查询语法或方法语法。一些方法,如 Append 或 Concat,在查询语法中没有对应的写法。
var words = new string[] { "falcon", "eagle", "sky", "tree", "water" };
// Query syntax
var res = from word in words
where word.Contains('a')
select word;
foreach (var word in res)
{
Console.WriteLine(word);
}
Console.WriteLine("-----------");
// Method syntax
var res2 = words.Where(word => word.Contains('a'));
foreach (var word in res2)
{
Console.WriteLine(word);
}
该示例使用查询语法和方法语法来找出所有包含字符 'a' 的单词。
// Query syntax
var res = from word in words
where word.Contains('a')
select word;
这是查询语法;它类似于 SQL 代码。
// Method syntax
var res2 = words.Where(word => word.Contains('a'));
这是方法语法;这些方法可以链式调用。
$ dotnet run falcon eagle water ----------- falcon eagle water
C# LINQ 元素访问
有一些辅助方法用于访问元素。
string[] words = { "falcon", "oak", "sky", "cloud", "tree", "tea", "water" };
Console.WriteLine(words.ElementAt(2));
Console.WriteLine(words.First());
Console.WriteLine(words.Last());
Console.WriteLine(words.First(word => word.Length == 3));
Console.WriteLine(words.Last(word => word.Length == 3));
在此示例中,我们访问数组的元素。
Console.WriteLine(words.ElementAt(2));
我们使用 ElementAt 获取数组中的第三个元素。
Console.WriteLine(words.First()); Console.WriteLine(words.Last());
我们检索数组的第一个和最后一个元素。
Console.WriteLine(words.First(word => word.Length == 3)); Console.WriteLine(words.Last(word => word.Length == 3));
First 和 Last 方法也可以接受一个谓词。我们获取第一个和最后一个长度为三个字符的元素。
$ dotnet run sky falcon water oak tea
Prepend 在序列的开头添加一个值,而 Append 在序列的末尾附加一个值。请注意,这些方法不会修改集合的元素。相反,它们会创建一个包含新元素的集合副本。
int[] vals = {1, 2, 3};
vals.Prepend(0);
vals.Append(4);
Console.WriteLine(string.Join(", ", vals));
var vals2 = vals.Prepend(0);
var vals3 = vals2.Append(4);
Console.WriteLine(string.Join(", ", vals3));
在此示例中,我们在整数数组的开头和末尾添加值。
$ dotnet run 1, 2, 3 0, 1, 2, 3, 4
C# LINQ select
select 子句或 Select 方法将序列的每个元素投影到一个新的形式中。它选择、投影和转换集合中的元素。Select 在其他语言中通常被称为 Map。
int[] vals = { 2, 4, 6, 8 };
var powered = vals.Select(e => Math.Pow(e, 2));
Console.WriteLine(string.Join(", ", powered));
string[] words = { "sky", "earth", "oak", "falcon" };
var wordLens = words.Select(e => e.Length);
Console.WriteLine(string.Join(", ", wordLens));
在此示例中,我们将一个整数数组转换为其幂的序列,并将一个单词数组转换为单词长度的序列。
$ dotnet run 4, 16, 36, 64 3, 5, 3, 6
C# LINQ select 到匿名类型
投影是从返回的对象中选择特定字段。投影是使用 select 子句完成的。我们可以将字段投影到匿名类型中。
User[] users =
{
new (1, "John", "London", "2001-04-01"),
new (2, "Lenny", "New York", "1997-12-11"),
new (3, "Andrew", "Boston", "1987-02-22"),
new (4, "Peter", "Prague", "1936-03-24"),
new (5, "Anna", "Bratislava", "1973-11-18"),
new (6, "Albert", "Bratislava", "1940-12-11"),
new (7, "Adam", "Trnava", "1983-12-01"),
new (8, "Robert", "Bratislava", "1935-05-15"),
new (9, "Robert", "Prague", "1998-03-14"),
};
var res = from user in users
where user.City == "Bratislava"
select new { user.Name, user.City };
Console.WriteLine(string.Join(", ", res));
record User(int id, string Name, string City, string DateOfBirth);
在此示例中,我们选择居住在 Bratislava 的用户。
var res = from user in users
where user.City == "Bratislava"
select new { user.Name, user.City };
通过 select new 子句,我们创建了一个具有两个字段的匿名类型:Name 和 City。
$ dotnet run
{ Name = Anna, City = Bratislava }, { Name = Albert, City = Bratislava },
{ Name = Robert, City = Bratislava }
C# LINQ SelectMany
SelectMany 将多个序列扁平化为一个单一序列。
int[][] vals = {
[1, 2, 3],
[4],
[5, 6, 6, 2, 7, 8],
};
var res = vals.SelectMany(nested => nested).OrderBy(x => x);
Console.WriteLine(string.Join(", ", res));
在此示例中,我们有一个数组的数组。通过 SelectMany 方法,我们将二维数组扁平化为一个一维数组。这些值也进行了排序。
$ dotnet run 1, 2, 2, 3, 4, 5, 6, 6, 7, 8
在下一个示例中,我们将嵌套列表扁平化为一个包含唯一值的单一列表。
var vals = new List<List<int>> {
new() {1, 2, 3, 3},
new() {4},
new() {5, 6, 6, 7, 7}
};
var res = vals.SelectMany(list => list)
.Distinct()
.OrderByDescending(e => e);
Console.WriteLine(string.Join(", ", res));
Distinct 方法用于获取唯一值。
$ dotnet run 7, 6, 5, 4, 3, 2, 1
C# LINQ Concat
Concat 方法连接两个序列。
User[] users1 =
{
new("John", "Doe", "gardener"),
new("Jane", "Doe", "teacher"),
new("Roger", "Roe", "driver")
};
User[] users2 =
{
new("Peter", "Smith", "teacher"),
new("Lucia", "Black", "accountant"),
new("Michael", "Novak", "programmer")
};
var allUsers = users1.Concat(users2);
foreach (var user in allUsers)
{
Console.WriteLine(user);
}
record User(string FirstName, string LastName, string Occupation);
我们有两个用户数组。我们使用 Concat 将它们合并。
$ dotnet run
User { FirstName = John, LastName = Doe, Occupation = gardener }
User { FirstName = Jane, LastName = Doe, Occupation = teacher }
User { FirstName = Roger, LastName = Roe, Occupation = driver }
User { FirstName = Peter, LastName = Smith, Occupation = teacher }
User { FirstName = Lucia, LastName = Black, Occupation = accountant }
User { FirstName = Michael, LastName = Novak, Occupation = programmer }
C# LINQ 筛选
我们可以使用 where 子句来筛选数据。条件可以使用 && 或 || 运算符进行组合。
List<string> words = [ "sky", "rock", "forest", "new",
"falcon", "jewelry", "eagle", "blue", "gray" ];
var query = from word in words
where word.Length == 4
select word;
foreach (var word in query)
{
Console.WriteLine(word);
}
在此示例中,我们挑选出所有具有四个字母的单词。
$ dotnet run rock blue gray
在我们的列表中,有三个单词满足该条件。
在下一个示例中,我们使用 || 运算符来组合条件。
List<string> words = [ "sky", "rock", "forest", "new",
"falcon", "jewelry", "small", "eagle", "blue", "gray" ];
var res = from word in words
where word.StartsWith('f') || word.StartsWith('s')
select word;
foreach (var word in res)
{
Console.WriteLine(word);
}
在此示例中,我们挑选出所有以 'f' 或 's' 字符开头的单词。
$ dotnet run sky forest falcon small
在下面的示例中,我们使用 && 应用两个条件。
List<Car> cars =
[
new ("Audi", 52642),
new ("Mercedes", 57127),
new ("Skoda", 9000),
new ("Volvo", 29000),
new ("Bentley", 350000),
new ("Citroen", 21000),
new ("Hummer", 41400),
new ("Volkswagen", 21600),
];
var res = from car in cars
where car.Price > 30000 && car.Price < 100000
select new { car.Name, car.Price };
foreach (var car in res)
{
Console.WriteLine($"{car.Name} {car.Price}");
}
record Car(string Name, int Price);
在此示例中,我们使用 where 子句筛选汽车对象列表。我们筛选出所有价格在 30000 到 100000 之间的汽车。
$ dotnet run Audi 52642 Mercedes 57127 Hummer 41400
C# LINQ 笛卡尔积
笛卡尔积是两个集合的乘积,用于形成所有有序对的集合。
char[] letters = "abcdefghi".ToCharArray();
char[] digits = "123456789".ToCharArray();
var coords =
from l in letters
from d in digits
select $"{l}{d}";
foreach (var coord in coords)
{
Console.Write($"{coord} ");
if (coord.EndsWith("9"))
{
Console.WriteLine();
}
}
Console.WriteLine();
在此示例中,我们创建字母和数字的笛卡尔积。
var coords =
from l in letters
from d in digits
select $"{l}{d}";
为了完成此任务,我们使用两个 from 子句。
$ dotnet run a1 a2 a3 a4 a5 a6 a7 a8 a9 b1 b2 b3 b4 b5 b6 b7 b8 b9 c1 c2 c3 c4 c5 c6 c7 c8 c9 d1 d2 d3 d4 d5 d6 d7 d8 d9 e1 e2 e3 e4 e5 e6 e7 e8 e9 f1 f2 f3 f4 f5 f6 f7 f8 f9 g1 g2 g3 g4 g5 g6 g7 g8 g9 h1 h2 h3 h4 h5 h6 h7 h8 h9 i1 i2 i3 i4 i5 i6 i7 i8 i9
C# LINQ Zip
Zip 方法从两个序列中获取元素并将它们组合起来。这些配对是根据相同位置的元素创建的。
string[] students = { "Adam", "John", "Lucia", "Tom" };
int[] scores = { 68, 56, 90, 86 };
var result = students.Zip(scores, (e1, e2) => e1 + "'s score: " + e2);
foreach (var user in result)
{
Console.WriteLine(user);
}
Console.WriteLine("---------------");
var left = new[] { 1, 2, 3 };
var right = new[] { 10, 20, 30 };
var products = left.Zip(right, (m, n) => m * n);
Console.WriteLine(string.Join(", ", products));
Console.WriteLine("---------------");
int[] codes = Enumerable.Range(1, 5).ToArray();
string[] states =
{
"Alabama",
"Alaska",
"Arizona",
"Arkansas",
"California"
};
var CodesWithStates = codes.Zip(states, (code, state) => code + ": " + state);
foreach (var item in CodesWithStates)
{
Console.WriteLine(item);
}
在此示例中,我们在三种情况下使用 Zip 方法。
string[] students = { "Adam", "John", "Lucia", "Tom" };
int[] scores = { 68, 56, 90, 86 };
var result = students.Zip(scores, (e1, e2) => e1 + "'s score: " + e2);
我们有一个学生数组和一个对应的分数数组。我们使用 Zip 将这两个数组组合成一个单一序列。e1 来自 students 数组,e2 来自 scores 数组;它们来自相同的位置。
var left = new[] { 1, 2, 3 };
var right = new[] { 10, 20, 30 };
var products = left.Zip(right, (m, n) => m * n);
在这里,我们创建了来自两个数组的值的乘积。
int[] codes = Enumerable.Range(1, 5).ToArray();
string[] states =
{
"Alabama",
"Alaska",
"Arizona",
"Arkansas",
"California"
};
var CodesWithStates = codes.Zip(states, (code, state) => code + ": " + state);
最后,我们为 states 数组的每个元素赋予一个编号。
$ dotnet run Adam's score: 68 John's score: 56 Lucia's score: 90 Tom's score: 86 --------------- 10, 40, 90 --------------- 1: Alabama 2: Alaska 3: Arizona 4: Arkansas 5: California
C# LINQ 内置聚合计算
LINQ 允许我们进行聚合计算,例如最小值、最大值或总和。
List<int> vals = [6, 2, -3, 4, -5, 9, 7, 8];
var n1 = vals.Count();
Console.WriteLine($"There are {n1} elements");
var n2 = vals.Count(e => e % 2 == 0);
Console.WriteLine($"There are {n2} even elements");
var sum = vals.Sum();
Console.WriteLine($"The sum of all values is: {sum}");
var s2 = vals.Sum(e => e > 0 ? e : 0);
Console.WriteLine($"The sum of all positive values is: {s2}");
var avg = vals.Average();
Console.WriteLine($"The average of values is: {avg}");
var max = vals.Max();
Console.WriteLine($"The maximum value is: {max}");
var min = vals.Min();
Console.WriteLine($"The minimum value is: {min}");
在此示例中,我们使用 Count、Sum、Average、Max 和 Min 方法。
$ dotnet run There are 8 elements There are 4 even elements The sum of all values is: 28 The sum of all positive values is: 36 The average of values is: 3.5 The maximum value is: 9 The minimum value is: -5
以下示例使用查询表达式。
List<int> vals = [6, 2, -3, 4, -5, 9, 7, 8];
var sum = vals.Aggregate((total, next) => total + next);
Console.WriteLine($"The sum is {sum}");
var product = vals.Aggregate((total, next) => total * next);
Console.WriteLine($"The product is {product}");
在此示例中,我们计算 vals 列表中的正值数量以及 words 列表中的字符数量。
$ dotnet run The sum of positive values is: 22 There are 18 letters in the list
C# LINQ 自定义聚合计算
自定义聚合计算可以使用 Aggregate 来完成。它在一个序列上应用一个累加器函数。
var vals = new List<int> { 6, 2, -3, 4, -5, 9, 7, 8 };
var sum = vals.Aggregate((total, next) => total + next);
Console.WriteLine($"The sum is {sum}");
var product = vals.Aggregate((total, next) => total * next);
Console.WriteLine($"The product is {product}");
在此示例中,我们使用 Aggregate 计算列表中值的总和与乘积。
$ dotnet run The sum is 28 The product is 362880
C# LINQ orderby
通过 OrderBy 方法或 orderby 子句,我们可以对序列的元素进行排序。
int[] vals = { 4, 5, 3, 2, 7, 0, 1, 6 };
var result = from e in vals
orderby e ascending
select e;
Console.WriteLine(string.Join(", ", result));
var result2 = from e in vals
orderby e descending
select e;
Console.WriteLine(string.Join(", ", result2));
在此示例中,我们按升序和降序对整数进行排序。ascending 关键字是可选的。
$ dotnet run 0, 1, 2, 3, 4, 5, 6, 7 7, 6, 5, 4, 3, 2, 1, 0
在下一个示例中,我们按多个字段对对象进行排序。
List<User> users =
[
new ("John", "Doe", 1230),
new ("Lucy", "Novak", 670),
new ("Ben", "Walter", 2050),
new ("Robin", "Brown", 2300),
new ("Amy", "Doe", 1250),
new ("Joe", "Draker", 1190),
new ("Janet", "Doe", 980),
new ("Albert", "Novak", 1930),
];
Console.WriteLine("sort ascending by last name and salary");
var sortedUsers = users.OrderBy(u => u.LastName).ThenBy(u => u.Salary);
foreach (var user in sortedUsers)
{
Console.WriteLine(user);
}
Console.WriteLine("---------------------");
Console.WriteLine("sort descending by last name and salary");
var sortedUsers2 = users.OrderByDescending(u => u.LastName)
.ThenByDescending(u => u.Salary);
foreach (var user in sortedUsers2)
{
Console.WriteLine(user);
}
record User(string FirstName, string LastName, int Salary);
在此示例中,我们首先按用户的姓氏排序,然后按他们的薪水排序。
var sortedUsers = users.OrderBy(u => u.LastName).ThenBy(u => u.Salary);
我们按用户的姓氏,然后按他们的薪水进行升序排序。
var sortedUsers2 = users.OrderByDescending(u => u.LastName)
.ThenByDescending(u => u.Salary);
在这里,我们按用户的姓氏,然后按他们的薪水进行降序排序。
$ dotnet run
sort ascending by last name and salary
User { FirstName = Robin, LastName = Brown, Salary = 2300 }
User { FirstName = Janet, LastName = Doe, Salary = 980 }
User { FirstName = John, LastName = Doe, Salary = 1230 }
User { FirstName = Amy, LastName = Doe, Salary = 1250 }
User { FirstName = Joe, LastName = Draker, Salary = 1190 }
User { FirstName = Lucy, LastName = Novak, Salary = 670 }
User { FirstName = Albert, LastName = Novak, Salary = 1930 }
User { FirstName = Ben, LastName = Walter, Salary = 2050 }
---------------------
sort descending by last name and salary
User { FirstName = Ben, LastName = Walter, Salary = 2050 }
User { FirstName = Albert, LastName = Novak, Salary = 1930 }
User { FirstName = Lucy, LastName = Novak, Salary = 670 }
User { FirstName = Joe, LastName = Draker, Salary = 1190 }
User { FirstName = Amy, LastName = Doe, Salary = 1250 }
User { FirstName = John, LastName = Doe, Salary = 1230 }
User { FirstName = Janet, LastName = Doe, Salary = 980 }
User { FirstName = Robin, LastName = Brown, Salary = 2300 }
C# LINQ MaxBy & MinBy
通过 MaxBy 和 MinBy 方法,我们可以根据给定的属性找到最大值和最小值。
List<User> users =
[
new ("John", "Doe", 1230),
new ("Lucy", "Novak", 670),
new ("Ben", "Walter", 2050),
new ("Robin", "Brown", 2300),
new ("Amy", "Doe", 1250),
new ("Joe", "Draker", 1190),
new ("Janet", "Doe", 980),
new ("Albert", "Novak", 1930),
];
Console.WriteLine("User with max salary:");
var u1 = users.MaxBy(u => u.Salary);
Console.WriteLine(u1);
Console.WriteLine("User with min salary:");
var u2 = users.MinBy(u => u.Salary);
Console.WriteLine(u2);
record User(string FirstName, string LastName, int Salary);
在此示例中,我们找到薪水最高和最低的用户。
$ dotnet run
User with max salary:
User { FirstName = Robin, LastName = Brown, Salary = 2300 }
User with min salary:
User { FirstName = Lucy, LastName = Novak, Salary = 670 }
C# LINQ Reverse
Reverse 方法反转序列中元素的顺序。(请注意,这与按降序排序不同。)
int[] vals = { 1, 3, 6, 0, -1, 2, 9, 9, 8 };
var reversed = vals.Reverse();
Console.WriteLine(string.Join(", ", reversed));
var reversed2 = (from val in vals select val).Reverse();
Console.WriteLine(string.Join(", ", reversed2));
在此示例中,我们使用方法语法和查询语法来反转数组的元素。
$ dotnet run 8, 9, 9, 2, -1, 0, 6, 3, 1 8, 9, 9, 2, -1, 0, 6, 3, 1
C# LINQ group by
我们可以根据某个键将数据分组到不同的类别中。
List<Car> cars =
[
new ("Audi", "red", 52642),
new ("Mercedes", "blue", 57127),
new ("Skoda", "black", 9000),
new ("Volvo", "red", 29000),
new ("Bentley", "yellow", 350000),
new ("Citroen", "white", 21000),
new ("Hummer", "black", 41400),
new ("Volkswagen", "white", 21600),
];
var groups = from car in cars
group car by car.Colour;
foreach (var group in groups)
{
Console.WriteLine(group.Key);
foreach (var car in group)
{
Console.WriteLine($" {car.Name} {car.Price}");
}
}
record Car(string Name, string Colour, int Price);
在此示例中,我们按颜色将可用的汽车分成几组。
$ dotnet run red Audi 52642 Volvo 29000 blue Mercedes 57127 black Skoda 9000 Hummer 41400 yellow Bentley 350000 white Citroen 21000 Volkswagen 21600
在下面的示例中,我们执行分组和聚合操作。
Revenue[] revenues =
{
new (1, "Q1", 2340),
new (2, "Q1", 1200),
new (3, "Q1", 980),
new (4, "Q2", 340),
new (5, "Q2", 780),
new (6, "Q3", 2010),
new (7, "Q3", 3370),
new (8, "Q4", 540),
};
var res = from revenue in revenues
group revenue by revenue.Quarter
into g
select new { Quarter = g.Key, Total = g.Sum(e => e.Amount) };
foreach (var line in res)
{
Console.WriteLine(line);
}
record Revenue(int Id, string Quarter, int Amount);
我们有四个季度的收入。我们按季度对收入进行分组,并对金额求和。
$ dotnet run
{ Quarter = Q1, Total = 4520 }
{ Quarter = Q2, Total = 1120 }
{ Quarter = Q3, Total = 5380 }
{ Quarter = Q4, Total = 540 }
我们可以使用 where 子句对聚合后的数据应用筛选器。
Revenue[] revenues =
{
new (1, "Q1", 2340),
new (2, "Q1", 1200),
new (3, "Q1", 980),
new (4, "Q2", 340),
new (5, "Q2", 780),
new (6, "Q3", 2010),
new (7, "Q3", 3370),
new (8, "Q4", 540),
};
var res = from revenue in revenues
group revenue by revenue.Quarter
into g
where g.Count() == 2
select new { Quarter = g.Key, Total = g.Sum(c => c.Amount) };
foreach (var line in res)
{
Console.WriteLine(line);
}
record Revenue(int Id, string Quarter, int Amount);
在此示例中,我们只选择那些恰好有两条收入记录的季度。
$ dotnet run
{ Quarter = Q2, Total = 1120 }
{ Quarter = Q3, Total = 5380 }
C# LINQ Chunk
chunk 方法将序列的元素拆分成指定大小的块。
List<int> vals = [1, 2, 3, 4, 5, 6, 7, 8];
IEnumerable<int[]> chunks = vals.Chunk(2);
int res = 0;
foreach (var chunk in chunks)
{
res += chunk[0] * chunk[1];
}
Console.WriteLine(res);
在此程序中,我们计算以下表达式:x1 * x2 + x3 * x4 ...
$ dotnet run 100
C# LINQ 词频统计
在下一个示例中,我们统计文件中单词的频率。
$ wget https://raw.githubusercontent.com/janbodnar/data/main/the-king-james-bible.txt
我们使用《英王钦定本圣经》。
using System.Text.RegularExpressions;
var fileName = "/home/janbodnar/Documents/the-king-james-bible.txt";
var text = File.ReadAllText(fileName);
var matches = new Regex("[a-z-A-Z']+").Matches(text);
var words = matches.Select(m => m.Value).ToList();
var res = words
.GroupBy(m => m)
.OrderByDescending(g => g.Count())
.Select(x => new { word = x.Key, Count = x.Count() })
.Take(10);
foreach (var r in res)
{
Console.WriteLine($"{r.word}: {r.Count}");
}
我们统计《英王钦定本圣经》中单词的频率。
var matches = new Regex("[a-z-A-Z']+").Matches(text);
var words = matches.Select(m => m.Value).ToList();
我们使用 Matches 方法找到所有匹配项。从匹配集合中,我们将所有单词放入一个列表中。
var res = words
.GroupBy(m => m)
.OrderByDescending(g => g.Count())
.Select(x => new { word = x.Key, Count = x.Count() })
.Take(10);
这些单词按频率进行分组并按降序排序。我们取频率最高的几个单词。
$ dotnet run the 62103 and 38848 of 34478 to 13400 And 12846 that 12576 in 12331 shall 9760 he 9665 unto 8942
C# LINQ join
join 子句用于连接序列。
string[] basketA = { "coin", "book", "fork", "cord", "needle" };
string[] basketB = { "watches", "coin", "pen", "book", "pencil" };
var res = from item1 in basketA
join item2 in basketB
on item1 equals item2
select item1;
foreach (var item in res)
{
Console.WriteLine(item);
}
在此示例中,我们有两个数组。通过 join 子句,我们找到两个数组中都存在的所有项目。
$ dotnet run coin book
单词 coin 和 book 都包含在两个数组中。
C# LINQ 分区
Skip 方法跳过序列开头指定数量的元素,并返回剩余的元素。SkipLast 方法返回省略了指定数量的最后几个元素的序列。SkipWhile 方法只要指定条件为真,就跳过序列中的元素,然后返回剩余的元素。
Take 方法从序列的开头返回指定数量的连续元素。TakeLast 方法省略除最后指定数量元素之外的所有元素。TakeWhile 方法只要指定条件为真,就从序列中返回元素,然后跳过剩余的元素。
SkipWhile 和 TakeWhile 方法在遇到第一个不匹配的元素时会停止。int[] vals = { 1, 2, 7, 8, 5, 6, 3, 4, 9, 10 };
var res1 = vals.Skip(3);
Console.WriteLine(string.Join(", ", res1));
var res2 = vals.SkipLast(3);
Console.WriteLine(string.Join(", ", res2));
var res3 = vals.SkipWhile(e => e < 5);
Console.WriteLine(string.Join(", ", res3));
Console.WriteLine("----------");
var res4 = vals.Take(3);
Console.WriteLine(string.Join(", ", res4));
var res5 = vals.TakeLast(3);
Console.WriteLine(string.Join(", ", res5));
var res6 = vals.TakeWhile(e => e < 5);
Console.WriteLine(string.Join(", ", res6));
该示例使用了所有六个分区方法。
$ dotnet run 8, 5, 6, 3, 4, 9, 10 1, 2, 7, 8, 5, 6, 3 7, 8, 5, 6, 3, 4, 9, 10 ---------- 1, 2, 7 4, 9, 10 1, 2
C# LINQ 转换
我们可以将返回的可枚举对象转换为列表、数组或字典。
User[] users =
{
new (1, "John", "London", "2001-04-01"),
new (2, "Lenny", "New York", "1997-12-11"),
new (3, "Andrew", "Boston", "1987-02-22"),
new (4, "Peter", "Prague", "1936-03-24"),
new (5, "Anna", "Bratislava", "1973-11-18"),
new (6, "Albert", "Bratislava", "1940-12-11"),
new (7, "Adam", "Trnava", "1983-12-01"),
new (8, "Robert", "Bratislava", "1935-05-15"),
new (9, "Robert", "Prague", "1998-03-14"),
};
string[] cities = (from user in users
select user.City).Distinct().ToArray();
Console.WriteLine(string.Join(", ", cities));
Console.WriteLine("------------");
List<User> inBratislava = (from user in users
where user.City == "Bratislava"
select user).ToList();
foreach (var user in inBratislava)
{
Console.WriteLine(user);
}
Console.WriteLine("------------");
Dictionary<int, string> userIds =
(from user in users
select user).ToDictionary(user => user.id, user => user.Name);
foreach (var kvp in userIds)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
record User(int id, string Name, string City, string DateOfBirth);
我们对数据源执行三个查询;生成的可枚举对象分别被转换为列表、数组和字典。
string[] cities = (from user in users
select user.City).Distinct().ToArray();
在此查询中,我们从数据源中选择所有城市。我们应用 Distinct 方法,最后调用 ToArray 方法。
List<User> inBratislava = (from user in users
where user.City == "Bratislava"
select user).ToList();
在这里,我们获取居住在 Bratislava 的用户列表;我们调用 ToList 方法。
Dictionary<int, string> userIds =
(from user in users
select user).ToDictionary(user => user.id, user => user.Name);
在此查询中,我们将用户名和他们的 ID 转换成一个字典。
$ dotnet run
London, New York, Boston, Prague, Bratislava, Trnava
------------
User { id = 5, Name = Anna, City = Bratislava, DateOfBirth = 1973-11-18 }
User { id = 6, Name = Albert, City = Bratislava, DateOfBirth = 1940-12-11 }
User { id = 8, Name = Robert, City = Bratislava, DateOfBirth = 1935-05-15 }
------------
1: John
2: Lenny
3: Andrew
4: Peter
5: Anna
6: Albert
7: Adam
8: Robert
9: Robert
C# LINQ 生成序列
Range、Repeat 和 Empty 方法可用于生成序列。
var res = Enumerable.Range(1, 10).Select(e => Math.Pow(e, 3));
Console.WriteLine(string.Join(", ", res));
Console.WriteLine("------------------");
int[] vals = { 8, 4, 3, 2, 5, 11, 15, 10, 3, 5, 6 };
var lines = vals.Select(e => Enumerable.Repeat("*", e)).ToArray();
foreach (var line in lines)
{
Console.WriteLine(string.Join("", line));
}
Console.WriteLine("------------------");
int[] nums = { 1, 3, 2, 3, 3, 3, 4, 4, 10, 10 };
var powered = nums.Aggregate(Enumerable.Empty<double>(), (total, next) =>
total.Append(Math.Pow(next, 2)));
foreach (var val in powered)
{
Console.WriteLine(val);
}
在此示例中,我们使用 Range、Repeat 和 Empty 方法来生成序列。
var res = Enumerable.Range(1, 10).Select(e => Math.Pow(e, 3));
我们使用 Range 生成 1 到 10 的整数,然后对它们进行立方运算。
int[] vals = { 8, 4, 3, 2, 5, 11, 15, 10, 3, 5, 6 };
var lines = vals.Select(e => Enumerable.Repeat("*", e)).ToArray();
借助于 Repeat 方法,我们为 vals 数组中的每个值生成一个水平条。
var powered = nums.Aggregate(Enumerable.Empty<double>(), (total, next) =>
total.Append(Math.Pow(next, 2)));
我们使用 Empty 方法为 Aggregate 方法创建一个空序列。
$ dotnet run 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000 ------------------ ******** **** *** ** ***** *********** *************** ********** *** ***** ****** ------------------ 1 9 4 9 9 9 16 16 100 100
C# LINQ 量词
通过量词,我们可以检查某些条件。
List<int> vals = [-1, -3, 0, 1, -3, 2, 9, -4];
bool positive = vals.Any(x => x > 0);
if (positive)
{
Console.WriteLine("There is a positive value");
}
bool allPositive = vals.All(x => x > 0);
if (allPositive)
{
Console.WriteLine("All values are positive");
}
bool hasSix = vals.Contains(6);
if (hasSix)
{
Console.WriteLine("6 value is in the array");
}
我们使用 Any 方法检查列表中是否有任何元素是正值。使用 All 方法,我们检查列表中的所有元素是否都是正值。最后,使用 Contains 方法,我们确定列表是否包含值 6。
C# LINQ 集合运算
LINQ 有执行集合运算的方法,包括 Union、Intersect、Except 和 Distinct。
var vals1 = "abcde".ToCharArray();
var vals2 = "defgh".ToCharArray();
var data = vals1.Union(vals2);
Console.WriteLine("{" + string.Join(" ", data) + "}");
var data2 = vals1.Intersect(vals2);
Console.WriteLine("{" + string.Join(" ", data2) + "}");
var data3 = vals1.Except(vals2);
Console.WriteLine("{" + string.Join(" ", data3) + "}");
int[] nums = { 1, 1, 2, 3, 4, 4, 4, 5, 6, 7, 7, 8 };
var data4 = nums.Distinct();
Console.WriteLine("{" + string.Join(" ", data4) + "}");
在此示例中,我们对数组元素进行集合运算。
var vals1 = "abcde".ToCharArray(); var vals2 = "defgh".ToCharArray();
借助于 ToCharArray,我们创建了两个字符数组。
var data = vals1.Union(vals2);
Union 产生两个数组的并集。
var data2 = vals1.Intersect(vals2);
Intersect 产生两个数组的交集。
var data3 = vals1.Except(vals2);
Except 产生两个数组的差集。
var data4 = nums.Distinct();
Distinct 从数组中返回不重复的元素。换句话说,它从数组创建一个集合。
$ dotnet run
{a b c d e f g h}
{d e}
{a b c}
{1 2 3 4 5 6 7 8}
C# LINQ XML
LINQ 可用于处理 XML。
using System.Xml.Linq;
string myXML = @"
<Users>
<User>
<Name>Jack</Name>
<Sex>male</Sex>
</User>
<User>
<Name>Paul</Name>
<Sex>male</Sex>
</User>
<User>
<Name>Frank</Name>
<Sex>male</Sex>
</User>
<User>
<Name>Martina</Name>
<Sex>female</Sex>
</User>
<User>
<Name>Lucia</Name>
<Sex>female</Sex>
</User>
</Users>";
var xdoc = new XDocument();
xdoc = XDocument.Parse(myXML);
var females = from u in xdoc.Root.Descendants()
where (string)u.Element("Sex") == "female"
select u.Element("Name");
foreach (var e in females)
{
Console.WriteLine("{0}", e);
}
我们解析 XML 数据并选择所有女性的名字。
$ dotnet run <Name>Martina</Name> <Name>Lucia</Name>
在本文中,我们学习了在 C# 中使用 LINQ。
作者
列出所有 C# 教程。