LINQ

Language Intergrated Query(Linq) 集成查询语言

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Concurrent;
  4. using System.Linq;
  5. namespace LinqTest
  6. {
  7. class MainClass
  8. {
  9. public static void Main(string[] args)
  10. {
  11. /*
  12. * where 筛选操作符定义了返回元素的条件
  13. *
  14. * Select 投射操作符用于把对象转换为另一个类型的新对象。
  15. *
  16. * OrderBy 排序操作符返回的元素的顺序
  17. * ThenBy
  18. * OerderByDescending
  19. * ThenByDescending
  20. * Reverse
  21. *
  22. * Join 连接操作符用于合并不直接相关的集合。
  23. * GroupJoin
  24. *
  25. * GroupBy 组合操作符把数组放在组中。
  26. * ToLookup
  27. *
  28. * Any 如果元素序列满足指定的条件,限定符操作就返回布尔值
  29. * All
  30. * Contains 检查某个元素是否在集合中
  31. *
  32. * Take 分区操作符返回集合的一个子集。指定要从集合中提取的元素个数
  33. * Skip 跳过指定的元素个数
  34. * TakeWhile
  35. * SkipWhile
  36. *
  37. * Distinct Set操作符返回一个集合。从集合中删除重复的元素。
  38. * Union 需要两个集合,返回出现在其中一个集合中的唯一元素。
  39. * Intersect 需要两个集合,返回两个集合都有的元素。
  40. * Except 需要两个集合,返回只出现在一个集合中的元素。
  41. * Zip 需要两个集合,把两个集合合并为一个
  42. *
  43. * First 这些元素操作符仅返回一个元素。返回第一个满足条件的元素。
  44. * FirstOrDefault 返回第一个满足条件的元素。如果没有找到,就返回默认值。
  45. * Last
  46. * LastOrDefault
  47. * ElementAt 指定了要返回的元素的位置
  48. * ElementAtOrDefault
  49. * Single 只返回一个满足条件的元素。如果有多个元素都满足条件,就抛出一个异常。
  50. * SingleOrDefault
  51. *
  52. * Count 聚合操作符计算集合的一个值。利用这些聚合操作符,可以计算所有值的总和、所有元素的个数
  53. * Sum 总和
  54. * Min 值最小的元素
  55. * Max 值最大的元素
  56. * Average 平均值
  57. * Aggregate 聚合 可以传递一个lambda表达式,该表达式对有所的值进行聚合
  58. *
  59. * ToArray 转换为数组
  60. * AsEnumerable 转换为IEnumerable
  61. * ToList 转换为IList
  62. * ToDictionary 转换为IDictionary
  63. * Cast<TResult>
  64. *
  65. * Empty 这些生成操作符返回一个新集合。使用Empty时集合是空的
  66. * Range 返回一系列数字
  67. * Repeat 返回一个始终重复一个值的集合;返回一个迭代器,该迭代器把同一个值重复特定的次数。
  68. *
  69. */
  70. // 1. 查询出 Unity1609 的所有冠军 并按照总分排序
  71. /*
  72. * 查询表达式必须以 from 子句开头,以 select 或 group 子句结束。
  73. * 在这两个子句之间,可以使用 where、oderby、join、let 和其他 from 子句。
  74. */
  75. var query = from n in Formula.GetChampions()
  76. where n.ClassesName == "Unity1609"
  77. orderby n.Sum descending
  78. select n;
  79. // 使用扩展方法实现
  80. var champions = new List<Student>(Formula.GetChampions());
  81. IEnumerable<Student> result = champions.Where(o => o.ClassesName == "Unity1609").
  82. OrderByDescending(o => o.Sum).
  83. Select(n => n);
  84. foreach (var item in result)
  85. {
  86. Console.WriteLine("{0:N}", item);
  87. }
  88. // 2. 筛选
  89. // 使用where子句,可以合并多个表达式。
  90. // 3. 用索引筛选
  91. // 不能使用LINQ查询的一个例子是Where()方法的重载。在Where()方法的重载时,可以传递第2个参数-索引。
  92. // 索引是筛选器返回的每个结果的计数器。
  93. // 4. 类型筛选
  94. // 为了进行基于类型的筛选,可以使用OfType<>()扩展方法。
  95. object[] data1 = { "one", 2, 3, "four" };
  96. var query1 = data1.OfType<string>(); // 从集合中仅返回字符串
  97. // 5. 复合的from子句
  98. // 如果需要根据对象的一个成员进行筛选,而该成员本身是一个系列,就可以使用复合的from子句。
  99. // 查询元素是集合的元素 扩展方法 SelectMany() 用于迭代序列的序列
  100. //var query2 = from r in Formula.GetChampions()
  101. //from c in r.Scores
  102. //where c > 90
  103. //orderby r.Sum
  104. //select r.Name + " " + r.Sum;
  105. var query2 = Formula.GetChampions().
  106. SelectMany(source => source.Scores, (source, collection) => new { Stu = source, Scores = collection }).
  107. Where(newObj => newObj.Stu.Sum > 90).
  108. OrderBy(newObj => newObj.Stu.Sum).
  109. Select(newObj => newObj.Stu.Name + " " + newObj.Stu.Sum);
  110. // 6. 排序
  111. // 要对序列排序,前面使用了orderby子句。
  112. // 7. 分组
  113. // 要根据一个关键字值对查询结果分组,可以使用group子句。
  114. //var query3 = from n in Formula.GetChampions()
  115. //group n by n.ClassesName into g
  116. //orderby g.Count(), g.Key
  117. //where g.Count() >= 2
  118. //select new
  119. //{
  120. // ClassName = g.Key,
  121. // Count = g.Count()
  122. //};
  123. // GroupBy() IGrouping Key
  124. var query3 = Formula.GetChampions().
  125. GroupBy(n => n.ClassesName).
  126. OrderByDescending(group => group.Count()).
  127. ThenBy(group => group.Key).
  128. Where(g => g.Count() >= 2).
  129. Select(g => new { ClassName = g.Key, Count = g.Count() });
  130. // 8. 对嵌套的对象分组
  131. // 如果分组的对象应包含嵌套的序列,就可以改变select子句创建的匿名类型
  132. var query4 = from r in Formula.GetChampions()
  133. group r by r.ClassesName into g
  134. orderby g.Count() descending, g.Key
  135. where g.Count() >= 2
  136. select new
  137. {
  138. ClassName = g.Key,
  139. Count = g.Count(),
  140. Scores = from r1 in g
  141. orderby r1.Sum descending, r1.Name
  142. select r1.Scores
  143. };
  144. foreach (var item in query4)
  145. {
  146. Console.WriteLine(item.ClassName + " " + item.Count);
  147. }
  148. // 9. 内连接
  149. // 使用join子句可以根据特定的条件合并两个数据源
  150. var query5 = from n in Formula.GetChampions()
  151. from score in n.Scores
  152. select new
  153. {
  154. Name = n.Name,
  155. Score = score,
  156. ClassName = n.ClassesName,
  157. Years = n.Years
  158. };
  159. var query6 = from n in Formula.GetContructorChampions()
  160. from y in n.Years
  161. select new
  162. {
  163. ClassName = n.ClassesName,
  164. Year = y
  165. };
  166. var query7 = (from n in query5
  167. join t in query6 on n.ClassName equals t.ClassName
  168. select new
  169. {
  170. n.ClassName,
  171. n.Name,
  172. n.Score,
  173. t.Year
  174. }).Take(10);
  175. foreach (var item in query7)
  176. {
  177. Console.WriteLine("{0},{1},{2},{3}", item.ClassName, item.Name, item.Score, item.Year);
  178. }
  179. // 10. 左外连接
  180. // 返回左边序列的全部元素,即时它们在右边的序列中没有匹配的元素
  181. // 使用 join into、 DefaultIfEmpty 定义
  182. var query8 = from n in query5
  183. join t in query6 on n.ClassName equals t.ClassName into rt
  184. from q in rt.DefaultIfEmpty()
  185. orderby n.Score
  186. select new
  187. {
  188. Name = n.Name,
  189. ClassName = q == null ? "NoClassName" : n.ClassName,
  190. Score = n.Score,
  191. Year = q == null ? -1 : q.Year
  192. };
  193. foreach (var item in query8)
  194. {
  195. Console.WriteLine($"{item.Name},{item.ClassName},{item.Score},{item.Year}");
  196. }
  197. // 11. 组连接
  198. // 左外连接使用了组连接和into子句。它有一部分语法与组连接相同。只不过组连接不使用DefaultIfEmpty方法。
  199. // 使用组连接时,可以连接两个独立的序列,对于其中一个序列中的某个元素,另一个序列中存在对应的一个项列表。
  200. var query9 = from r in query5
  201. from y in r.Years
  202. join r2 in query6 on
  203. new { ClassName = r.ClassName, Year = y }
  204. equals
  205. new { ClassName = r2.ClassName, Year = r2.Year }
  206. into g
  207. select new { ClassName = r.ClassName, Name = r.Name, Year = y, Score = r.Score };
  208. foreach (var item in query9)
  209. {
  210. Console.WriteLine($"{item.ClassName},{item.Name},{item.Score},{item.Year}");
  211. }
  212. // 12. 集合操作
  213. // 扩展方法 Distinct()、Union()、Intersect()和Except() 都是集合操作。
  214. // 13. 合并
  215. // Zip()方法允许用一个谓词函数把两个相关的序列合并为一个。
  216. var query10 = query5.Zip(query6, (first, second) => first.ClassName + "," + first.Name + "," + second.Year);
  217. // 14. 分区
  218. // 扩展方法 Take() 和 Skip() 等的分区操作可用于分页。
  219. // 使用 TakeWhile() 和 SkipWhile() 扩展方法,还可以传递一个谓词,根据谓词的结果提取或跳过某些项。
  220. // 15. 聚合操作符
  221. // 聚合操作符(如Count()、Sum()、Min()、Max()、Average()和Aggregate())不返回一个序列,而返回一个值。
  222. var query11 = from r in Formula.GetChampions()
  223. let scoreCount = r.Scores.Count() // let子句定义了一个变量
  224. where scoreCount >= 2
  225. orderby scoreCount descending
  226. select new
  227. {
  228. Name = r.Name,
  229. ClassName = r.ClassesName
  230. };
  231. // 16. 转换操作符
  232. // ToList()
  233. // Lookup<TKey, TElement> 一个键可以对应多个值
  234. var query12 = (from r in Formula.GetChampions()
  235. from s in r.Scores
  236. select new
  237. {
  238. ClassName = r.ClassesName,
  239. Name = r.Name,
  240. Score = s
  241. }).ToLookup(cr => cr.ClassName, cr => cr.Name);
  242. foreach (var item in query12["Unity1605"])
  243. {
  244. Console.WriteLine($"{item}");
  245. }
  246. // 如果需要在非类型化的集合上(如ArrayList)使用LINQ查询,就可以使用Cast()方法。
  247. var list = new System.Collections.ArrayList(Formula.GetChampions() as System.Collections.ICollection);
  248. var query13 = from r in list.Cast<Student>()
  249. where r.ClassesName == "Unity1605"
  250. select r;
  251. foreach (var item in query13)
  252. {
  253. Console.WriteLine($"{item.ClassesName}, {item.Name}");
  254. }
  255. // 17. 生成操作符
  256. // 生成操作符Range()、Empty()和Repeat()不是扩展方法,而是返回序列的正常静态方法。
  257. //var values = Enumerable.Range(1, 20); // 第一个参数作为起始值,第二个参数作为要填充的项数
  258. //values = values.Select(n => n * 3);
  259. //foreach (var item in values)
  260. //{
  261. // Console.WriteLine(item);
  262. //}
  263. // Range()方法不返回填充了所定义值的集合,这个方法与其他方法一样,也推迟执行查询,
  264. // 并返回一个RangeEnumerator,其中只有一条yield return 语句,来递增值。
  265. // 18. 并行LINQ
  266. // System.Linq名称空间中包含的类ParalleEnumerable可以分解查询的工作,使其分布在多个线程上。
  267. //var res = (from x in SampleData().AsParallel()
  268. //where Math.Log(x) < 4
  269. //select x).Average();
  270. var res = SampleData().AsParallel().Where(x => Math.Log(x) < 4).Select(x => x).Average();
  271. Console.WriteLine(res);
  272. // 19. 分区器
  273. // AsParalle() 方法不仅扩展了IEnuerable<T> 接口,还扩展了 Partitioner 类。通过它,可以影响要创建的分区。
  274. // Partitioner 类用 System.Collection.ConCurrent 名称空间定义,并且有不同的变体。
  275. // Create() 方法接受实现了 IList<T> 类的数组或对象。
  276. var data = SampleData();
  277. var res1 = (from x in Partitioner.Create(data).AsParallel()
  278. where Math.Log(x) < 4
  279. select x).Average();
  280. Console.WriteLine(res1);
  281. // 也可以调用 WithExecutionMode() 和 WithDegreeOfParallelism() 方法,来影响并行机制。
  282. // 20. 取消
  283. // .NET 提供了一种标准方式,来取消长时间运行的任务,这也适用于并行LINQ
  284. // 要取消长时间运行的查询,可以给查询添加WithCancellation()方法,并传递一个CancellationToken令牌作为参数。
  285. // CancellationToken令牌从CancellationTokenSource类中创建。
  286. // 该查询在单独的线程中运行,在该线程中,捕获一个OperationCanceledException类型的异常。
  287. // 如果取消了查询,就触发这个异常。在主线程中,调用CancellationTokenSource类的Cancel()方法可以取消任务。
  288. // 21. 表达式树 Expression<T>
  289. }
  290. static IEnumerable<int> SampleData()
  291. {
  292. const int arraySize = 100000000;
  293. var r = new Random();
  294. return Enumerable.Range(0, arraySize).Select(x => r.Next(140)).ToList();
  295. }
  296. }
  297. #region 扩展方法
  298. /*
  299. * 扩展方法 : 定义为静态方法,其第一个参数定义了它扩展的类型,扩展方法在一个静态类中声明。
  300. * 为了区分扩展方法和一般的静态方法,扩展方法还需要对第一个参数使用 this 关键字。
  301. *
  302. * 扩展方法不能访问它扩展的类型的私有成员。调用扩展方法只是调用静态方法的一种新语法。
  303. * string s = "hello";
  304. * s.Foo();
  305. * StringExtension.Foo(s);
  306. */
  307. public static class StringExtension
  308. {
  309. public static void Foo(this string s)
  310. {
  311. Console.WriteLine("Foo invoked for {0}", s);
  312. }
  313. }
  314. #endregion
  315. // 2016~2018年间,所有获得第一名的名单
  316. static class Formula
  317. {
  318. public static List<Student> nameList;
  319. public static IList<Student> GetChampions()
  320. {
  321. if (nameList == null)
  322. {
  323. nameList = new List<Student>()
  324. {
  325. new Student("张三", 18, "Unity1605", new int[]{99, 98}, new int[]{2016}),
  326. new Student("李四", 20, "Unity1605", new int[]{98, 97}, new int[]{2016}),
  327. new Student("李四", 20, "Unity1607", new int[]{98, 97}, new int[]{2016}),
  328. new Student("刘五", 21, "Unity1609", new int[]{98, 96}, new int[]{2016}),
  329. new Student("王二", 21, "Unity1609", new int[]{99, 96}, new int[]{2016}),
  330. new Student("王六", 22, "Unity1609", new int[]{94, 95}, new int[]{2017}),
  331. new Student("赵六", 23, "Unity1701", new int[]{96, 96}, new int[]{2017}),
  332. new Student("田七", 23, "Unity1711", new int[]{95, 97}, new int[]{2017}),
  333. new Student("孙二", 22, "Unity1711", new int[]{94, 98}, new int[]{2018}),
  334. new Student("王八", 21, "Unity1801", new int[]{99, 99}, new int[]{2018}),
  335. new Student("王八", 21, "Unity1805", new int[]{99, 99}, new int[]{2018}),
  336. };
  337. }
  338. return nameList;
  339. }
  340. private static List<ClassesChampion> teams;
  341. public static IList<ClassesChampion> GetContructorChampions()
  342. {
  343. if (teams == null)
  344. {
  345. teams = new List<ClassesChampion>()
  346. {
  347. new ClassesChampion("Unity1605", 2016),
  348. new ClassesChampion("Unity1607", 2016),
  349. new ClassesChampion("Unity1609", 2016, 2017),
  350. new ClassesChampion("Unity1701", 2017),
  351. new ClassesChampion("Unity1711", 2017, 2018),
  352. new ClassesChampion("Unity1801", 2018),
  353. new ClassesChampion("Unity1803", 2019)
  354. };
  355. }
  356. return teams;
  357. }
  358. }
  359. // 排行榜 获得第一名学员的班级名字和年份
  360. class ClassesChampion
  361. {
  362. public ClassesChampion(string name, params int[] years)
  363. {
  364. this.ClassesName = name;
  365. this.Years = new List<int>(years);
  366. }
  367. public string ClassesName { get; private set; }
  368. public IEnumerable<int> Years { get; private set; }
  369. }
  370. class Student : IComparable<Student>, IFormattable
  371. {
  372. public string Name { get; private set; }
  373. public string ClassesName { get; private set; }
  374. public int Age { get; set; }
  375. public int Sum { get; private set; }
  376. public IEnumerable<int> Scores { get; private set; }
  377. public IEnumerable<int> Years { get; private set; } // 获得第一名的年份
  378. public Student(string name, int age, string classesName) : this(name, age, classesName, null, null) { }
  379. public Student(string name, int age, string classesName, IEnumerable<int> scores, IEnumerable<int> years)
  380. {
  381. this.Name = name;
  382. this.ClassesName = classesName;
  383. this.Age = age;
  384. this.Scores = new List<int>(scores);
  385. this.Years = new List<int>(years);
  386. foreach (var item in Scores)
  387. {
  388. Sum += item;
  389. }
  390. }
  391. public override string ToString()
  392. {
  393. return string.Format("[Student: Name={0}, Age={1}]", Name, Age);
  394. }
  395. public string ToString(string format)
  396. {
  397. return ToString(format, null);
  398. }
  399. public string ToString(string format, IFormatProvider formatProvider)
  400. {
  401. switch (format)
  402. {
  403. case "N":
  404. return this.Name;
  405. default:
  406. return ToString();
  407. }
  408. }
  409. public int CompareTo(Student other)
  410. {
  411. if (other == null) return 1;
  412. return this.Sum - other.Sum;
  413. }
  414. }
  415. }

  1. // Aggregate 用法
  2. // ---------------------------- 一、对第1个和第2个元素执行操作,将执行的结果继续携带进行操作 ----------------
  3. // 求1~100的和
  4. var list = Enumerable.Range(1, 100);
  5. var sum = list.Aggregate((a, b) => a + b);
  6. Console.WriteLine(sum);
  7. // ---------------------------- 二、种子重载 --------------------------------------------------------
  8. // 先从种子开始作为第一个元素执行操作。
  9. // 求1~5的阶乘
  10. var nums = Enumerable.Range(2, 4); // 2 3 4 5
  11. var sum1 = nums.Aggregate(1, (a, b) => a * b);
  12. Console.WriteLine(sum1);
  13. // 翻转单词
  14. string content = "i am hxsd";
  15. content = content.Split(' ').Aggregate((a, b)=> b + " " + a );
  16. Console.WriteLine(content);
  17. // --------------------------- 三、结果选择器 --------------------------------------------------------
  18. // 最长的字符串 输出大写
  19. string[] fruits = { "apple", "mango", "orange", "passionfruit" };
  20. string longestName = fruits.Aggregate(fruits[0], (longest, next) => longest.Length < next.Length ? next : longest, n => n.ToUpper());
  21. Console.WriteLine(longestName);