参考书:《 visual C# 从入门到精通》
第三部分 用C#定义可扩展类型
第21章 使用查询表达式来查询内存中的数据
文章目录
21.1 什么是LINQ
LINQ,Language INtegrated Query,语言集成查询,对应用程序代码中查询数据的机制进行“抽象”。
LINQ的语法和语义和SQL很像。但LINQ更灵活,且能处理范围更大的逻辑数据结构。
21.2 在C#应用程序中使用LINQ
LINQ要求数据用实现了IEnumerable
或iEnumerable<T>
接口的数据结构进行存储。可以使用数组、HashSet、Queue或其他集合类型,要求这种类型是可枚举的。如:
var customers=new[]{
new{CustomerID=1,FirstName="Kim",LastName="Abercromble",CompanyName="Alpine Ski House"},
...;
};
var addresses=new[]{
new{CompanyName="Alpine Ski House",City="Berne",Country="Switzerland"},
...;
}
21.2.1 选择数据
一下代码显式由customers
数组中FirstName
组成的列表:
IEnumerable<string>customerFirstName=customers.Select(cust=>cust.FirstName);
foreach(string name in customerFirstName){
Console.WriteLine(name);
}
Select
方法允许从数组获取特定信息,传给Select
方法的参数是另一个方法。注意:
- cust 是传给方法的参数
- Select方法目前还没有开始获取数据,它只是返回一个可枚举的对象。之后遍历时才会真正获取由Select指定的数据
- Select不是Array类型的方法,它是Enumerable类的扩展方法。Enumerable类位于
System.Linq
命名空间,它提供了大量的静态方法来查询实现了泛型IEnumerable<T>
接口的对象
Select
方法的定义如下:
public static IEnumerable<TResult> Select<TSource.TResult>(
this.IEnumerable<TSource> source,Func<TSource,TResult> selector)
表明Select
是泛型方法,需要获取两个类型参数和两个普通参数。TSource
是要为其生成可枚举结果的集合,TResult
是可枚举结果集中的数据。Select
是扩展方法。
简单的说就是Select
方法返回基于某具体类型的可枚举集合。
如果需要返回多个数据项,例如需要返回FirstName和LastName,有下面几个方案可选:
- 将二者的字符串连接起来:
IEnumerable<string> customerNames=customers.Select(cust=>$"{cust.FirstName} {cust.LastName}");
- 可以定义一种新类型来封装名字和姓氏:
class FullName{
public string FirstName{get;set;}
public string LastName{get;set;}
}
...;
IEnumerable<Names>customerName=customers.Select(cust=>new FullName{
FirstName=cust.FirstName,LastName=cust.LastName
});
- 也可以使用匿名类型,就不需要专门定义一个新的类型了,
var customerName=customers.Select(cust=>new{FirstName=cust.FirstName,LastName=cust.LastName});
21.2.2 筛选数据
用Where
方法筛选数据:
IEnumerable<string> usCompanies=address.Where(addr=>String.Equals(addr.Country,"United states"))
.Select(usComp=>usComp.CompanyName);
foreach(string name in usCompanies){
Console.WriteLine(name);
}
21.2.3 排序、分组和聚合数据
OrderBy
:
IEnumberable<string>companyNames=address.OrderBy(addr=>addr.CompanyName).Select(comp.CompanyName);
foreach(string name in companyName)
COnsole.WriteLine(name);
要降序的话用OrderByDescending
。要按多个键来排序可以在OrderBy
或OrderByDescending
后使用ThenBy
或ThenByDescending
。
按一个或多个字段中共同的值对数据进行分组,可以使用GroupBy
。
var companiesGroupByCountry=addresses.GroupyBy(addrs=>addrs.Country);
foreach(var companiesPerCountry in companiesGroupByCountry){
Console.WriteLine($"Country:{companiesPerCountry.Key}\t{companiesPerCountry.Count()}companies");
foreach(var companies in companiesPerCountry){
Console.WriteLine($"\t{companies.CompanyName}");
}
}
GroupBy
后面不用Select
将字段投射到结果。
可以直接为Select
方法的结果使用很多汇总的方法,如Count
、Max
和Min
等。
int num=addresses.Select(addr=>addr.CompanyName).Count();
Console.WriteLine($"Number of companies: {num}");
注意Count
是不会区分重复的值。如果要不重复计数,可以用Distinct
方法删除重复:
int num=addresses.Select(addr=>addr.Country).Distinct().Count();
Console.WriteLine($"Number of countries :{num}");
21.2.4 联结数据
var com=customers.Select(c=>new{c.FirstName,c.LastName,c.CompanyName})
.Join(addresses,custs=>custs.CompanyName,addrs=>addrs.CompanyName,
(custs,addrs)=>new{custs.FirstName,custs.LastName,addrs.Country});
foreach(var row in com){
Console.WriteLine(row);
}
21.2.5 使用查询操作符
用查询操作符from
和select
:
var cust=from cust in customers
select cust.FirstName;
编译器会将上述表达式解析成对应的Select
方法。
var cust=from c in customers
select new {c.FirstName,c.LastName};
var usCompanies=from a in addresses
where String.Equals(a.Country,"United States")
select a.CompanyName;
var companyNames=from a in addresses
orderby a.CompanyName
select a.CompanyName;
var comp=from a in addresses
group a by a.Country;
int num=(from a in addresses
select a.CompanyName).Count();
int nums=(from a in addresses
select a.Country).Distrinct().Count();
var citiesAndCustomers=from a in addresses
join c in customers
on a.CompanyName equals c.CompanyName
select new{c.FirstName,c.LastName,a.Country};
21.2.6 查询Tree<TItem>对象中的数据
下面是简单的实例,用查询表达式来查询我们的二叉树中的数据。
先新建一个简答的类:
```employee.cs`
using System;
using System.Collections.Generic;
using System.Text;
namespace C_21_2_6
{
class Employee:IComparable<Employee>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Department { get; set; }
public int Id { get; set; }
public override string ToString()
{
return $"Id; {this.Id}, Name: {this.FirstName} {this.LastName}, Dept: {this.Department}";
}
int IComparable<Employee>.CompareTo(Employee other)
{
if (other == null)
return 1;
if (this.Id > other.Id)
return 1;
if (this.Id < other.Id)
return -1;
return 0;
}
}
}
可以通过右击项目添加现有项的方式添加之前创建的二叉树类Tree.cs
,
Program.cs
中的代码如下:
using c_19_1_1;
using System;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
namespace C_21_2_6
{
class Program
{
static void dowork()
{
Tree<Employee>emptree=new Tree<Employee>(
new Employee { Id=1,FirstName="Kim",LastName="Abercromble",Department="IT"});
emptree.Insert(new Employee { Id = 2, FirstName = "Jeff", LastName = "Hay", Department = "Marketting" });
emptree.Insert(new Employee { Id = 4, FirstName = "Charlie", LastName = "Herb", Department = "IT" });
emptree.Insert(new Employee { Id = 6, FirstName = "Chris", LastName = "Preston", Department = "Sales" });
emptree.Insert(new Employee { Id = 3, FirstName = "Dava", LastName = "Barnett", Department = "Sales" });
emptree.Insert(new Employee { Id = 5, FirstName = "Tim", LastName = "Litton", Department = "Marketing" });
Console.WriteLine("List of departments");
var depts = emptree.Select(d => d.Department).Distinct();
foreach (var dept in depts)
Console.WriteLine($"Department: {dept}");
Console.WriteLine("\nEmployees in the IT department");
var ITEmployees = emptree.Where(e => String.Equals(e.Department, "IT")).Select(emp => emp);
foreach (var emp in ITEmployees)
Console.WriteLine(emp);
Console.WriteLine("\nAll employees grouped by department");
var employeesByDept = emptree.GroupBy(e => e.Department);
foreach(var dept in employeesByDept)
{
Console.WriteLine($"Department :{dept.Key}");
foreach (var emp in dept)
Console.WriteLine($"\t{emp.FirstName} {emp.LastName}");
}
Console.WriteLine("\n查询操作符:\n");
//var depts = emptree.Select(d => d.Department).Distinct();
var deptss = (from d in emptree
select d.Department).Distinct();
foreach (var dept in deptss)
Console.WriteLine($"Department: {dept}");
Console.WriteLine("\nEmployees in the IT department");
//var ITEmployees = emptree.Where(e => String.Equals(e.Department, "IT")).Select(emp => emp);
var ITemployeess = from e in emptree
where String.Equals(e.Department, "IT")
select e;
foreach (var emp in ITemployeess)
Console.WriteLine(emp);
Console.WriteLine("\nAll employees grouped by department");
//var employeesByDept = emptree.GroupBy(e => e.Department);
var employeesByDepts = from e in emptree
group e by e.Department;
foreach (var dept in employeesByDepts)
{
Console.WriteLine($"Department :{dept.Key}");
foreach (var emp in dept)
Console.WriteLine($"\t{emp.FirstName} {emp.LastName}");
}
}
static void Main(string[] args)
{
dowork();
}
}
}
运行结果为:
List of departments
Department: IT
Department: Marketting
Department: Sales
Department: Marketing
Employees in the IT department
Id; 1, Name: Kim Abercromble, Dept: IT
Id; 4, Name: Charlie Herb, Dept: IT
All employees grouped by department
Department :IT
Kim Abercromble
Charlie Herb
Department :Marketting
Jeff Hay
Department :Sales
Dava Barnett
Chris Preston
Department :Marketing
Tim Litton
查询操作符:
Department: IT
Department: Marketting
Department: Sales
Department: Marketing
Employees in the IT department
Id; 1, Name: Kim Abercromble, Dept: IT
Id; 4, Name: Charlie Herb, Dept: IT
All employees grouped by department
Department :IT
Kim Abercromble
Charlie Herb
Department :Marketting
Jeff Hay
Department :Sales
Dava Barnett
Chris Preston
Department :Marketing
Tim Litton
C:\Users\xhh\Source\Repos\C_21_2_6\C_21_2_6\bin\Debug\netcoreapp3.1\C_21_2_6.exe (进程 2008)已退出,代码为 0。
按任意键关闭此窗口. . .
21.2.7 LINQ和推迟求值
注意一点,从执行一个LINQ查询到遍历集合之间,原始集合中的数据有可能会发生变化的,但遍历获取的结果始终是根据最新的数据的结果。所以如果我们先定义一个LINQ查询,然后修改集合,最后获取数据时得到最新的数据,这个策略就是推迟求值。
如我们将刚才的应用程序中的Program.cs
中的代码修改一下来验证这一点:
using c_19_1_1;
using System;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
namespace C_21_2_6
{
class Program
{
static void dowork()
{
Tree<Employee>emptree=new Tree<Employee>(
new Employee { Id=1,FirstName="Kim",LastName="Abercromble",Department="IT"});
emptree.Insert(new Employee { Id = 2, FirstName = "Jeff", LastName = "Hay", Department = "Marketting" });
emptree.Insert(new Employee { Id = 4, FirstName = "Charlie", LastName = "Herb", Department = "IT" });
emptree.Insert(new Employee { Id = 6, FirstName = "Chris", LastName = "Preston", Department = "Sales" });
emptree.Insert(new Employee { Id = 3, FirstName = "Dava", LastName = "Barnett", Department = "Sales" });
emptree.Insert(new Employee { Id = 5, FirstName = "Tim", LastName = "Litton", Department = "Marketing" });
Console.WriteLine("All employees");
var allemployees = from e in emptree
select e;
foreach (var emp in allemployees)
Console.WriteLine(emp);
emptree.Insert(new Employee { Id = 7, FirstName = "David", LastName = "Simpson", Department = "IT" });
Console.WriteLine("\nEmployee added");
Console.WriteLine("All employees");
foreach (var emp in allemployees)
Console.WriteLine(emp);
}
static void Main(string[] args)
{
dowork();
}
}
}
运行结果:
All employees
Id; 1, Name: Kim Abercromble, Dept: IT
Id; 2, Name: Jeff Hay, Dept: Marketting
Id; 3, Name: Dava Barnett, Dept: Sales
Id; 4, Name: Charlie Herb, Dept: IT
Id; 5, Name: Tim Litton, Dept: Marketing
Id; 6, Name: Chris Preston, Dept: Sales
Employee added
All employees
Id; 1, Name: Kim Abercromble, Dept: IT
Id; 2, Name: Jeff Hay, Dept: Marketting
Id; 3, Name: Dava Barnett, Dept: Sales
Id; 4, Name: Charlie Herb, Dept: IT
Id; 5, Name: Tim Litton, Dept: Marketing
Id; 6, Name: Chris Preston, Dept: Sales
Id; 7, Name: David Simpson, Dept: IT
C:\Users\xhh\Source\Repos\C_21_2_6\C_21_2_6\bin\Debug\netcoreapp3.1\C_21_2_6.exe (进程 10036)已退出,代码为 0。
按任意键关闭此窗口. . .