目录
一、LINQ
1. LINQ介绍
- LINQ(发音link),代表语言集成查询。
- LINQ是.NET框架的扩展,它允许我们使用SQL查询数据库的方式查询数据集合。
- 使用LINQ,可以从数据库、程序对象的集合、以及XML文档中查询数据。
- LINQ数据源是支持泛型IEnumerable<T>接口或从中继承的接口的任意对象。
- 延迟查询:查询的实际执行将推迟在foreach语句中循环访问查询变量之后进行。
- 强制立即执行:执行聚合函数的查询必须首先循环访问这些元素,可使用ToList或ToArray来强制立即执行任何查询并缓存其结果。
代码案例:
internal class Program
{
static void Main(string[] args)
{
//1.数据源
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//2.创建查询
IEnumerable<int> query = from n in nums where n > 5 select n;
//3.执行查询
foreach (int n in query)
Console.WriteLine(n);
}
}
上述代码中仅通过创建查询不会检索到任何数据,执行查询是在foreach中发生。(延迟查询)
2. 匿名类型
- 匿名类型是可以创建无名类型的一种类型,经常用于LINQ查询的结果中。
- 匿名类型只能和局部变量配合使用,不能用于类成员。
- 匿名类型没有名字,必须使用var关键字作为变量类型。
- 编译器为匿名对象创建的属性是只读的,所以不能设置匿名类型对象的属性。
- 格式:
代码示例:
class Other
{
public static string Name = "光头强";
}
internal class Program
{
static void Main(string[] args)
{
string Major = "体育";
var man = new { Age = 25, Other.Name, Major };
Console.WriteLine("Age:{0}, Name:{1}, Major:{2}", man.Age, man.Name, man.Major);
}
}
二、方法语法和查询语法
1. 初识查询语法和方法语法。
- 我们使用LINQ查询时可以使用两种形式的语法:方法语法和查询语法。
- 方法语法:使用标准的方法调用,这些方法是一组叫做标准查询运算符的方法。方法语法命令式的,指明了查询方法调用的顺序。
- 查询语法:类似于SQL语句,使用查询表达式形式书写。查询语法是声明式的,查询描述的是你想返回的东西,但并没有指明如何执行这个查询。
internal class Program
{
static void Main(string[] args)
{
int[] ints = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//查询语法
IEnumerable<int> numsQuery = from n in ints
where n > 4 && n < 8
select n;
//方法语法
IEnumerable<int> methodQuery = ints.Where(x => (x > 4 && x < 8));//返回枚举
//两种形式的组合
int count = (from n in ints where n > 4 && n < 8 select n).Count();//返回单个值
foreach (int i in methodQuery)
Console.Write(i);
Console.WriteLine();
foreach (int i in numsQuery)
Console.Write(i);
Console.WriteLine();
Console.Write(count);
}
}
2. 查询变量
- LINQ查询可以返回两种类型的结果,如下:
- 枚举:包含了一组数据
- 标量:一个单一的值
- 理解查询变量:
- 如果查询表达式返回枚举,查询一直到处理枚举时才会执行。(延迟执行)
- 如果枚举被处理多次,查询就会执行多次。
- 如果在进行遍历之后,查询执行之前数据有改动,则查询会使用新的数据。
- 如果查询表达式返回标量,查询立即执行,并把结果保存在查询变量中。
三、查询表达式的结构
查询表达式由查询体后的from子句组成。
- 子句必须按照一定的顺序。
- from子句和select...group这两部分是必需的。
- 其他子句是可选的。
- 可以有任意多的from...let...where子句。
1. from子句
- from子句和foreach子句格式十分相似,但实际有很大区别:
- foreach语句命令式指定了按顺序一个个访问集合中的项。from子句只是声明式地规定集合中的每个项都要访问,并没有指定顺序。
- foreach在遇到代码时就执行其主体。from子句什么也不执行,只有在遇到访问查询变量的语句时才会执行。
- 格式:
- 代码示例:
internal class Program
{
static void Main(string[] args)
{
int[] ints = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var nums = from n in ints
where n > 7 //使用迭代变量n
select n; //使用迭代变量n
foreach (var n in nums)
Console.WriteLine(n);
}
}
2. join子句
- 注意事项:
- 使用联结来结合两个或更多集合中的数据。
- 联结对象接受两个集合,然后创建一个临时的对象集合,每一个对象包含原始集合对象中的所有字段。
- 字段只能使用equals比较,不能使用==
- 格式:
- 代码示例:
class Student
{
public int StID;
public string LastName;
}
class CourseStudent
{
public string CourseName;
public int StID;
}
internal class Program
{
//初始化数组
static Student[] students = new Student[] {
new Student { StID = 1,LastName="光头强" },
new Student { StID = 2,LastName="熊大" },
new Student { StID = 3,LastName="熊二" }
};
static CourseStudent[] courseStudents = new CourseStudent[] {
new CourseStudent { StID = 1,CourseName="体育"},
new CourseStudent { StID = 2,CourseName="历史"},
new CourseStudent { StID = 2,CourseName="化学"},
new CourseStudent { StID = 3,CourseName="化学"},
new CourseStudent { StID = 1,CourseName="历史"},
new CourseStudent { StID = 3,CourseName="体育"},
};
static void Main(string[] args)
{
//查询所有选择了历史课的学生
var query = from s in students
join c in courseStudents on s.StID equals c.StID
where c.CourseName == "历史"
select s.LastName;
foreach (var e in query)
Console.WriteLine(e);
}
}
- 代码图解:
3. 查询主体中的from...let...where片段
可选的from...let...where部分是查询主体的第一部分,可以由任意数量的from子句、let子句、where子句来组合。
1.多个from子句
internal class MyClass
{
static void Main(string[] args)
{
var groupA = new[] { 3, 4, 5, 6 };
var groupB = new[] { 6, 7, 8, 9 };
var someInts = from a in groupA //必需的第一个from子句
from b in groupB //查询主体的第一个from子句
where a > 4 && b <= 8
select new { a, b, sum = a + b };//匿名类型对象
foreach (var e in someInts)
Console.WriteLine(e);
}
}
2.let子句
let子句接受一个表达式的运算并且把它赋值给一个需要在其他运算中使用的标识符。
internal class MyClass
{
static void Main(string[] args)
{
var groupA = new[] { 3, 4, 5, 6 };
var groupB = new[] { 6, 7, 8, 9 };
var someInts = from a in groupA
from b in groupB
let sum = a + b
where sum == 12
select new { a, b, sum };
foreach (var e in someInts)
Console.WriteLine(e);
}
}
3.多个where子句
where子句根据之后的运算来去除不符合条件的项。
internal class MyClass
{
static void Main(string[] args)
{
var groupA = new[] { 3, 4, 5, 6 };
var groupB = new[] { 6, 7, 8, 9 };
var someInts = from a in groupA
from b in groupB
let sum = a + b
where sum >= 11 //选择sum>=11的
where a == 4 //选择a==4的
select new { a, b, sum };//存放满足两种条件的匿名类
foreach (var e in someInts)
Console.WriteLine(e);
}
}
4. orderby子句
orderby接受一个表达式,并根据表达式按顺序返回结果项。orderby子句默认升序,可以使用ascending和descending来指定升序还是降序
internal class MyClass
{
static void Main(string[] args)
{
//匿名类型的对象数组
var students = new[]
{
new { Name = "光头强", Age = 25, Course = "体育" },
new { Name = "熊大", Age = 17, Course = "美术" },
new { Name = "熊二", Age = 16, Course = "历史" },
new { Name = "喜羊羊", Age = 13, Course = "历史" },
new { Name = "懒羊羊", Age = 14, Course = "历史" },
new { Name = "沸羊羊", Age = 15, Course = "历史" },
};
var stu = from s in students
orderby s.Age
select s;
foreach ( var student in stu )
Console.WriteLine("{0},{1},{2}",student.Name,student.Age,student.Course);
}
}
5. select子句
select子句指定所选对象的哪部分应该被选择。
internal class MyClass
{
static void Main(string[] args)
{
//匿名类型的对象数组
var students = new[]
{
new { Name = "光头强", Age = 25, Course = "体育" },
new { Name = "熊大", Age = 17, Course = "美术" },
new { Name = "熊二", Age = 16, Course = "历史" },
new { Name = "喜羊羊", Age = 13, Course = "历史" },
new { Name = "懒羊羊", Age = 14, Course = "历史" },
new { Name = "沸羊羊", Age = 15, Course = "历史" },
};
var stu = from s in students
select s.Name; //只选择数据项中的学生名字
foreach ( var student in stu )
Console.WriteLine("{0}",student);
}
}
6. 查询中的匿名类型
可以在select子句后创建匿名类型存放指定查询的数据。
internal class MyClass
{
static void Main(string[] args)
{
//匿名类型的对象数组
var students = new[]
{
new { Name = "光头强", Age = 25, Course = "体育" },
new { Name = "熊大", Age = 17, Course = "美术" },
new { Name = "熊二", Age = 16, Course = "历史" },
new { Name = "喜羊羊", Age = 13, Course = "历史" },
new { Name = "懒羊羊", Age = 14, Course = "历史" },
new { Name = "沸羊羊", Age = 15, Course = "历史" },
};
var stu = from s in students
select new { s.Name, s.Age };//创建匿名类型存放需要的数据
foreach ( var student in stu )
Console.WriteLine("{0}\t{1}",student.Name,student.Age);
}
}
7. group子句
group子句把select的对象根据一些标准进行分组。
internal class MyClass
{
static void Main(string[] args)
{
//匿名类型的对象数组
var students = new[]
{
new { Name = "光头强", Age = 25, Course = "体育" },
new { Name = "熊大", Age = 17, Course = "美术" },
new { Name = "熊二", Age = 16, Course = "历史" },
new { Name = "喜羊羊", Age = 13, Course = "历史" },
new { Name = "懒羊羊", Age = 14, Course = "历史" },
new { Name = "沸羊羊", Age = 15, Course = "历史" },
};
var stus = from s in students
group s by s.Course;//根据学科进行分组
foreach (var stu in stus)
{
Console.WriteLine(stu.Key);//分组键
foreach (var s in stu)
Console.WriteLine(" {0},{1}",s.Name,s.Age);
}
}
}
图解:
8. 查询延续:into子句
查询延续子句可以接受查询的一部分结果并赋予一个名字,从而可以在查询的另一部分中使用。
internal class MyClass
{
static void Main(string[] args)
{
int[] groupA = { 1, 2, 3, 4, 5, 6, 7, 8, };
int[] groupB = { 5, 6, 7 };
//将groupA、groupB的联合查询的结果延续一个新名字groupAandB,然后继续新的查询
IEnumerable<int> ints = from a in groupA
join b in groupB on a equals b
into groupAandB
from c in groupAandB
select c;
foreach (int i in ints)
Console.WriteLine(i);
}
}
四、标准查询运算符
标准查询运算符由一系列API方法组成,它们可以让我们查询任何.NET数组或集合。
- 被查询的集合序列叫做对象,必须实现IEnumerable<T>接口。
- 标准查询运算符使用方法语法。
- 一些运算符返回IEnumerable对象,一些运算符返回标量。
- 很多操作都已一个谓词作为参数,谓词是一个方法,它以对象作为参数,根据对象是否满足某个条件而返回true或false。
- 共有47个标准查询运算符,可用来操作一个或多个序列,序列是指实现了IEnumerable<T>接口的类。
- 标准查询运算符都是IEnumerable<T>的扩展方法,所以实现了IEnumerable<T>的类可以像调用自己的方法一样调用这些标准查询运算符。
五、将委托作为参数
1. LINQ预定义泛型委托
很多LINQ运算符需要我们提供代码来指示运算符如何执行它的操作,我们通过把委托对象作为参数来实现。LINQ定义了套泛型委托类型与标准查询运算符一起使用,即Func委托和Action委托,各有17个成员。
2.预定义泛型委托和标准运算符配合使用案例
internal class MyClass
{
static bool IsOdd(int x) //委托对象使用的方法
{
return x % 2 == 1;
}
static void Main(string[] args)
{
int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Func<int, bool> func = new Func<int, bool>(IsOdd);//谓词:根据对象是否满足某个条件返回true或false
int count = arr.Count(func);
Console.WriteLine("奇数个数为:{0}",count);
}
}
3. Lambda表达式代替委托的情景
可以选择使用委托来搭配标准运算符的情景:
- 当初始化委托的方法在其他地方也有使用,
- 函数体中的代码多于一句
如果上述两条都不成立,那么就可以使用Lambda表达式来简化
//使用Lambda表达式简化使用委托来获取奇数个数的代码
internal class MyClass
{
static void Main(string[] args)
{
int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int count = arr.Count(x => x % 2 == 1);
Console.WriteLine("奇数个数为:{0}",count);
}
}
(注:本章学习总结自《C#图解教程》)