參考書:《 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。
按任意鍵關閉此窗口. . .