C# LINQ 查询表达式
最后修改于 2023 年 7 月 5 日
C# LINQ 查询表达式教程展示了如何使用查询在 C# 中提取和转换数据。
语言集成查询 (LINQ) 是一组基于将查询功能直接集成到 C# 语言中的技术。
查询是一组指令,描述了从给定数据源(或多个数据源)检索哪些数据,以及返回的数据应该具有什么形状和组织结构。 LINQ 查询表达式可用于方便地从数组、可枚举类、XML 文档、关系数据库和第三方数据源中提取和处理数据。
查询表达式可用于查询和转换来自任何启用 LINQ 的数据源的数据。 查询表达式具有延迟执行的特性。 它们只有在我们迭代查询变量时才会被执行,例如在 foreach 语句中。
C# 查询表达式过滤器
在接下来的示例中,我们将展示如何过滤数据。
int[] vals = { -2, 4, 6, -1, 2, 0, 1, -3, -4, 2, 3, 8 };
var pos =
from val in vals
where val > 0
select val;
Console.WriteLine(string.Join(" ", pos));
var evens =
from val in vals
where val % 2 == 0
select val;
Console.WriteLine(string.Join(" ", evens));
在该示例中,我们从数组中过滤掉正值和偶数值。
var pos =
from val in vals
where val > 0
select val;
使用 from 子句,我们遍历数组的元素。 这些元素使用 where 进行过滤,最后使用 select 进行投影。
$ dotnet run 4 6 2 1 2 3 8 -2 4 6 2 0 -4 2 8
接下来,我们使用 || 运算符组合条件。
var words = new List<string> { "sky", "bee", "forest", "new", "falcon", "rock",
"cloud", "war", "small", "eagle", "blue", "frost", "water" };
var found =
from word in words
where word.StartsWith("f") || word.StartsWith("w")
select word;
foreach (var word in found)
{
Console.WriteLine(word);
}
我们在 where 子句中应用两个条件。 我们获取以 'f' 或 'w' 开头的单词。
$ dotnet run forest falcon war frost water
在下面的示例中,我们使用 && 应用两个条件。
var cars = new List<Car>
{
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# 查询 first/last
我们使用 First 和 Last 方法获取可枚举对象的第一个和最后一个元素。
string[] words = { "falcon", "oak", "sky", "cloud", "tree", "tea", "water" };
var first = (from word in words
where word.Length == 3
select word).First();
Console.WriteLine(first);
var last = (from word in words
where word.Length == 3
select word).Last();
Console.WriteLine(last);
在该示例中,我们访问数组的元素。
var first = (from word in words
where word.Length == 3
select word).First();
我们获取长度为 3 的第一个元素。
var last = (from word in words
where word.Length == 3
select word).Last();
我们检索长度为 3 的最后一个元素。
$ dotnet run oak tea
C# 查询 select
select 子句将序列的每个元素投影成一种新的形式。 它选择、投影和转换集合中的元素。 在其他语言中,select 通常被称为 Map。
int[] vals = { 2, 4, 6, 8 };
var powered =
from val in vals
select Math.Pow(val, 2);
Console.WriteLine(string.Join(", ", powered));
string[] words = { "sky", "earth", "oak", "falcon" };
var wordLens =
from word in words
select word.Length;
Console.WriteLine(string.Join(", ", wordLens));
在该示例中,我们将整数数组转换为其幂的序列,并将单词数组转换为单词长度的序列。
$ dotnet run 4, 16, 36, 64 3, 5, 3, 6
C# 查询 select into 匿名类型
投影是从返回的对象中选择特定的字段。 投影是通过 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);
在该示例中,我们选择居住在布拉迪斯拉发的用户。
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# 查询扁平化数组
以下示例将数组的数组扁平化为单个数组。
int[][] vals = {
new[] {1, 2, 3},
new[] {4},
new[] {5, 6, 6, 2, 7, 8},
};
var res = (from nested in vals
from e in nested
select e);
Console.WriteLine(string.Join(", ", res));
要扁平化数组,我们使用两个 from 子句。
$ dotnet run 1, 2, 2, 3, 4, 5, 6, 6, 7, 8
C# 查询 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 users = (from u1 in users1 select u1).Concat(from u2 in users2 select u2);
foreach (var user in users)
{
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# 查询笛卡尔积
笛卡尔积是两个集合的乘积,形成所有有序对的集合。
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# 查询 Count & Sum
Count 返回序列中元素的数量。 Sum 计算数值序列的总和。
var content =
@"Foxes are omnivorous mammals belonging to several genera
of the family Canidae. Foxes have a flattened skull, upright triangular ears,
a pointed, slightly upturned snout, and a long bushy tail. Foxes live on every
continent except Antarctica. By far the most common and widespread species of
fox is the red fox.";
var lines = content.Split("\n");
var n1 = (from line in lines
select line).Count();
Console.WriteLine($"There are {n1} lines");
var words = content.Split(" ");
var n2 = (from word in words
select word).Count();
Console.WriteLine($"There are {n2} words");
var chars = content.ToCharArray();
var n3 = (from c in chars
where c == 'f'
select c).Count();
Console.WriteLine($"There are {n3} f letters");
在该示例中,我们使用 Count 方法来计算文本中的行数、单词数和 'f' 字符数。
$ dotnet run There are 5 lines There are 47 words There are 7 f letters
在下一个示例中,我们使用 Sum 方法。
var vals = new List<int> { 1, -2, 3, -4, 5, 6, 7, -8 };
var s = (from x in vals where x > 0 select x).Sum();
Console.WriteLine($"The sum of positive values is: {s}");
var words = new List<string> { "falcon", "eagle", "hawk", "owl" };
int len = (from x in words select x.Length).Sum();
Console.WriteLine($"There are {len} letters in the list");
在该示例中,我们计算 vals 列表中正值的数量以及 words 列表中字符的数量。
$ dotnet run The sum of positive values is: 22 There are 18 letters in the list
C# 查询 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
在下一个示例中,我们按多个字段对对象进行排序。
var users = new List<User>
{
new ("Robert", "Novak", 1770),
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 ("Peter", "Novak", 990),
new ("Albert", "Novak", 1930),
};
Console.WriteLine("sort descending by last name and salary");
var sortedUsers = from user in users
orderby user.LastName descending, user.Salary descending
select user;
foreach (var user in sortedUsers)
{
Console.WriteLine(user);
}
Console.WriteLine("-------------------------");
Console.WriteLine("sort ascending by last name and salary");
var sortedUsers2 = from user in users
orderby user.LastName ascending, user.Salary ascending
select user;
foreach (var user in sortedUsers2)
{
Console.WriteLine(user);
}
record User(string FirstName, string LastName, int Salary);
在该示例中,首先按用户的姓氏,然后按他们的工资对用户进行排序。
var sortedUsers = from user in users
orderby user.LastName descending, user.Salary descending
select user;
在这里,我们按用户的姓氏,然后按他们的薪水进行降序排序。
var sortedUsers2 = from user in users
orderby user.LastName ascending, user.Salary ascending
select user;
在这里,我们按用户的姓氏排序,然后按工资升序排序。
$ dotnet run
sort descending by last name and salary
User { FirstName = Ben, LastName = Walter, Salary = 2050 }
User { FirstName = Albert, LastName = Novak, Salary = 1930 }
User { FirstName = Robert, LastName = Novak, Salary = 1770 }
User { FirstName = Peter, LastName = Novak, Salary = 990 }
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 }
-------------------------
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 = Peter, LastName = Novak, Salary = 990 }
User { FirstName = Robert, LastName = Novak, Salary = 1770 }
User { FirstName = Albert, LastName = Novak, Salary = 1930 }
User { FirstName = Ben, LastName = Walter, Salary = 2050 }
C# 查询 Reverse
Reverse 方法反转序列中元素的顺序。(请注意,这与按降序排序不同。)
int[] vals = { 1, 3, 6, 0, -1, 2, 9, 9, 8 };
var reversed = (from val in vals select val).Reverse();
Console.WriteLine(string.Join(", ", reversed));
在该示例中,我们使用方法和查询语法反转数组的元素。
$ dotnet run 8, 9, 9, 2, -1, 0, 6, 3, 1
C# LINQ group by
我们可以根据某个键将数据分组到不同的类别中。
var cars = new List<Car>
{
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 词频统计
在下一个示例中,我们统计文件中单词的频率。
$ wget https://raw.githubusercontent.com/janbodnar/data/main/the-king-james-bible.txt
我们使用《英王钦定本圣经》。
using System.Text.RegularExpressions;
var fileName = "the-king-james-bible.txt";
var text = File.ReadAllText(fileName);
var dig = new Regex(@"\d");
var matches = new Regex("[a-z-A-Z']+").Matches(text);
var words =
from match in matches
let val = match.Value
where !dig.IsMatch(val)
select match.Value;
var topTen =
(from word in words
group word by word into wg
orderby wg.Count() descending
select new {word = wg.Key, Total = wg.Count()}
).Take(10);
foreach (var e in topTen)
{
Console.WriteLine($"{e.word}: {e.Total}");
}
我们统计《英王钦定本圣经》中单词的频率。
var matches = new Regex("[a-z-A-Z']+").Matches(text);
var words = matches.Select(m => m.Value).ToList();
我们使用 Matches 方法找到所有匹配项。从匹配集合中,我们将所有单词放入一个列表中。
var words =
from match in matches
let val = match.Value
where !dig.IsMatch(val)
select match.Value;
在第一个查询中,我们找到所有匹配项。 从匹配集合中,我们得到所有单词。
var topTen =
(from word in words
group word by word into wg
orderby wg.Count() descending
select new {word = wg.Key, Total = wg.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
硬币和书籍单词包含在两个数组中。
C# 查询转换
我们可以将返回的可枚举对象转换为列表、数组或字典。
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();
在这里,我们获得居住在布拉迪斯拉发的用户的列表; 我们调用 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# 查询 XML
LINQ 可用于处理 XML。
using System.Xml.Linq;
string myXML = @"
<Users>
<User>
<Name>Jack Moore</Name>
<Occupation>programmer</Occupation>
</User>
<User>
<Name>Paul Novak</Name>
<Occupation>driver</Occupation>
</User>
<User>
<Name>Frank Woody</Name>
<Occupation>teacher</Occupation>
</User>
<User>
<Name>Martina Doe</Name>
<Occupation>programmer</Occupation>
</User>
<User>
<Name>Lucia Black</Name>
<Occupation>teacher</Occupation>
</User>
</Users>";
var xdoc = new XDocument();
xdoc = XDocument.Parse(myXML);
var data = from u in xdoc.Root.Descendants()
where (string)u.Element("Occupation") == "teacher"
select u.Element("Name");
foreach (var e in data)
{
Console.WriteLine($"{e}");
}
我们解析 XML 数据并选择所有女性姓名。
$ dotnet run <Name>Frank Woody</Name> <Name>Lucia Black</Name>
C# 查询列出目录内容
Directory.EnumerateFiles 返回满足指定条件的完整文件名的可枚举集合。
var path = "/home/user2/";
var files = from file in Directory.EnumerateFiles(path, "*.txt",
SearchOption.AllDirectories)
where Path.GetFileName(file).ToLower().Contains("data")
select file;
foreach (var file in files)
{
Console.WriteLine("{0}", file);
}
Console.WriteLine("{0} files found.", files.Count<string>().ToString());
该示例递归搜索所有名称包含单词 data 的文本文件。
C# 查询 let 子句
let 子句允许我们存储子表达式的结果,以便在后续子句中使用它。
John Doe, gardener, 12/5/1997 Jane Doe, teacher, 5/16/1983 Robert Smith, driver, 4/2/2001 Maria Smith, cook, 9/21/1976
这些是 data.csv 文件的内容。
using System.Text;
var path = "data.csv";
var lines = File.ReadLines(path, Encoding.UTF8);
var users = from line in lines
let fields = line.Replace(", ", ",").Split(",")
select new User(fields[0], fields[1], DateTime.Parse(fields[2]));
var sorted = from user in users
orderby user.DateOfBirth descending
select user;
foreach (var user in sorted)
{
Console.WriteLine(user);
}
public record User(string Name, string Occupation, DateTime DateOfBirth);
在该示例中,我们解析 data.csv 文件并创建一个用户序列; 这些用户按出生日期降序排序。
var users = from line in lines
let fields = line.Replace(", ", ",").Split(",")
select new User(fields[0], fields[1], DateTime.Parse(fields[2]));
在第一个查询表达式中,我们将一行拆分为其字段; 这些字段存储在 fields 变量中,该变量稍后在 select 子句中使用。
$ dotnet run
User { Name = Robert Smith, Occupation = driver, DateOfBirth = 4/2/2001 12:00:00 AM }
User { Name = John Doe, Occupation = gardener, DateOfBirth = 12/5/1997 12:00:00 AM }
User { Name = Jane Doe, Occupation = teacher, DateOfBirth = 5/16/1983 12:00:00 AM }
User { Name = Maria Smith, Occupation = cook, DateOfBirth = 9/21/1976 12:00:00 AM }
在本文中,我们使用了 C# 中的 LINQ 查询表达式。
来源
作者
列出所有 C# 教程。