stream
。
演示用数据:
Teacher fg = new Teacher { Name = "大飞哥", Age = 41 };
Teacher fish = new Teacher { Name = "小鱼", Age = 28 };
Teacher waiting = new Teacher { Name = "诚聘" };
IEnumerable<Teacher> teachers = new List<Teacher> { fg, fish, waiting };
Major csharp = new Major { Name = "C#", Teacher = fg };
Major SQL = new Major { Name = "SQL", Teacher = fg };
Major Javascript = new Major { Name = "Javascript", Teacher = fg };
Major UI = new Major { Name = "UI", Teacher = fish };
IEnumerable<Major> majors = new List<Major> { csharp, SQL, Javascript, UI };
IList<Student> students = new List<Student>
new Student{Score = 98, Name = "屿", Majors=new List<Major>{csharp,SQL } },
new Student{Score = 86, Name = "行人", Majors=new List<Major>{Javascript, csharp, SQL} },
new Student{Score = 78, Name = "王平", Majors=new List<Major>{csharp}},
new Student{Score = 89, Name = "王枫", Majors=new List<Major>{Javascript, csharp, SQL,UI}},
new Student{Score = 98, Name = "蒋宜蒙", Majors=new List<Major>{Javascript, csharp}},
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
可以由任何IEnumerable<TSource>对象(比如List<Student>)调用。(#体会#:泛型和继承/多态的作用)
Func<TSource, bool> predicate,代表的是过滤/筛选条件:
基于TSource(集合元素)进行运算,返回一个bool值,确定是否满足要求。
任何一个能返回bool值的、传入参数为TSource的lambda表达式都可以用作where条件(尤其是在Linq to Object中):
s => s.Name.StartsWith("王")
s => s.Name.StartsWith("王") && s.Score > 80
//甚至实际上不用s
s => true
PS:不要因为Lambda的参数是Student类型,不是int等简单类型就懵了
IEnumerable<TSource>,(惯例)Linq运算的结果可以用var声明,因为以后这种类型可能会变得越来越臃肿复杂。
然后,使用foreach循环输出:
foreach (var item in excellents)
Console.WriteLine(item.Name);
MimicWhere()方法
Linq(to Object)的本质还是foreach。复习:面向函数中的filter函数
为了加深对上述Linq方法的理解(兼复习),我们自己来写一个Where条件的方法。
static class Mimic
internal static IEnumerable<T> MimicWhere<T>(
this IEnumerable<T> source, Func<T, bool> predicate)
foreach (var item in source)
if (predicate(item))
yield return item;
注意:使用yield避免额外的集合声明
注意:尽量直接使用OrderBy()或OrderByDescending(),不要在OrderBy()之后再Reverse(),养成习惯,尤其是在后面Linq to SQL/EF时,注意会造成性能浪费。(暂时理解成:先排序再颠倒不如一次性排序)
自定义比较
排序是依赖于比较的,int、string等都是天然可比较的(实现了IComparable)
演示:用“不可比较”的属性进行排序,会报错:
students.OrderByDescending(s => s.Majors);
因为.NET运行时会懵:两个List我咋比较?
如果我们有确定的比较方案,比如比较他们元素的个数,可以
首先需要自己声明一个实现IComparer的类:
class MajorComparor<T> : IComparer<IList<T>>
public int Compare([AllowNull] IList<T> x, [AllowNull] IList<T> y)
return x.Count() - y.Count();
然后传入其对象:
students.OrderByDescending(s => s.Majors, new MajorComparor<Major>());
ThenBy()
实现先按某字段(比如Score)排序,当Score成绩相同时,再按另一个字段(比如Majors)排序的功能:
var excellents = students.OrderBy(s=>s.Score)
.ThenBy(s => s.Majors, new MajorComparor<Major>());
不要使用:OrderBy().OrderBy(),这样前面一个OrderBy()会被后面一个OrderBy()覆盖……
ToList()/ToArray()/ToXXX等方法:将IEnumerable转换成List/数组/其他集合对象
List<Student> list = excellents.ToList();
Student[] array = excellents.ToArray();
First()/Single()/Last(),获取第一个/唯一一个/最后一个元素。
演示:如果说
不止一个元素的话,Single()会报错
一个都没有的话,First()和Last()会报错
如果不想抛异常的话,可以加后缀OrDefault:
Console.WriteLine(excellents.SingleOrDefault()?.Name);
意思是:如果没有符合条件的元素,返回该元素类型的默认值
引用类型null值(复习)
其他(0,false……)略
Sum/Count/Average/Min/Max:求和/元素个数/平均值/最小值/最大值
Console.WriteLine(excellents.Sum(s=>s.Score));
//区分Count属性,Count()方法里还能传过滤条件
Console.WriteLine(excellents.Count(/*s=>s.Name.StartsWith("王")*/));
Console.WriteLine(excellents.Average(s=>s.Score));
Console.WriteLine(excellents.Max(s=>s.Score));
Console.WriteLine(excellents.Min(s=>s.Score));
两个原因:
保证获取的是“即时”(up-to-date)数据
//假设students(数据源)发生了变化
students.Add(new Student { Score = 65, Name = "" });
在进行多条件查询拼接时提高性能,使用最终表达式一次遍历,完成所有查询
foreach (var item in groupedMajor)
Console.WriteLine(item.Key.GetType() + ":" + item.Key.Name);
foreach (var i in item)
Console.WriteLine(" " + i.GetType() + ":" + i.Name);
Console.WriteLine();
有时候我们需要用多个属性进行分组,这时候用匿名对象即可:
majors.GroupBy(m => new { m.Name, m.Age });
ToDictionary()
更多的时候,我们是利用聚合函数进行统计,得到各个小组的:数量/最大/最小/和/平均值等……
foreach (var item in groupedMajor)
Console.WriteLine(item.Key.Name + ":" + item.Count());
但上述结果我们不直接输出,而是将其存储起来,如何操作?
可以用ToDictionary(),将老师(名称)做键,课程数量做值:
Dictionary<string, int> pairs =
groupedMajor.ToDictionary(gm => gm.Key.Name, gm => gm.Count());
然后在select的时候:
IEnumerable<Score> scores = students.Select(
s => new Score { Name = s.Name, Value = s.Score });
还可以使用
复习:匿名类
但此时只能使用var声明变量:
其实在Linq to Object中并不必要,因为类的关联,总有一些其他办法。但我们还是应该掌握以下用法,以备日后Linq to SQL所用
public class Major
//public Teacher Teacher { get; internal set; }
public string Teacher { get; internal set; }
//Major csharp = new Major { Name = "C#", Teacher = fg };
Major csharp = new Major { Name = "C#", Teacher = "大飞哥" };
如何获取上述集合?这就需要用Join(),连接若干个集合:
var tm = majors.Join(teachers, //majors连接teachers
m => m.Teacher, t => t.Name, //连接依赖/比较的属性
(m, t) => new //生成的匿名对象作为结果集合元素
MajorName = m.Name,
TeacherName = t.Name,
TeacherAge = t.Age
PS:Join方法不如Linq表达式有表现力,所以此处只简单介绍。
思路就是先把所有学生的所有课程取出来,将之前的Student:List<Major>(1:n)变成Student:Major(1:1),然后再进行过滤:
var result = students.SelectMany(
s => s.Majors, //指示取出students里的所有Major
(s, m) => new { Student = s, MajorName = m.Name } //组合student和major
.Where(sm => sm.MajorName.ToLower().Contains("s")) //在上述结果集中筛选
foreach (var item in result)
Console.WriteLine($"{item.Student.Name}:{item.MajorName}");
形象理解:将原集合中每个元素中“横向”的转“纵向”