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
方法获取可枚举的第一个和最后一个元素。
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
在其他语言中通常称为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
子句执行的。我们可以将字段投影到匿名类型中。
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 };
通过selectnew
子句,我们创建了一个具有两个字段的匿名类型: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
计算一系列数值的总和。
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
子句,我们可以对序列的元素进行排序。
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
方法反转序列中元素的顺序。(请注意,这与按降序排序不同。)
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分组方式
我们可以根据某个键将数据分组。
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}"); }
我们计算KingJames圣经中单词的出现频率。
var matches = new Regex("[a-z-A-Z']+").Matches(text); var words = matches.Select(m => m.Value).ToList();
我们通过Matches
方法找到所有匹配项。从matchcollection中,我们将所有单词放入一个列表中。
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
子句连接序列。
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#教程。