05-C#

发布时间:2026/5/20 2:17:45

05-C# C#.Net-Lambda与LINQ-学习笔记一、Lambda 表达式的演变Lambda 表达式是委托的语法糖本质上是匿名方法的简化写法编译器最终会将其编译为委托实例。.NET 1.0/1.1 — 命名方法委托public delegate void NoReturnWithPara(int x, string y); NoReturnWithPara method new NoReturnWithPara(Study); method.Invoke(123, Richard); private void Study(int id, string name) { Console.WriteLine(${id} {name} 学习.Net高级班); }缺点方法必须单独定义代码分散不够内聚。.NET 2.0 — 匿名方法引入delegate关键字方法体可以直接内联并且能访问外部局部变量(闭包)int i 0; NoReturnWithPara method new NoReturnWithPara(delegate (int x, string y) { Console.WriteLine(x); Console.WriteLine(y); Console.WriteLine(i); // 可以访问外部变量 });.NET 3.0 — Lambda 表达式用操作符(读作 goes to)替代delegate关键字语法更简洁// 完整形式 NoReturnWithPara method (int x, string y) { Console.WriteLine(x); Console.WriteLine(y); }; // 省略参数类型(编译器自动推导) NoReturnWithPara method (x, y) { Console.WriteLine(x); };Lambda 简化规则规则从上到下依次叠加// 方法体只有一行省略大括号 NoReturnWithPara method1 (x, y) Console.WriteLine(x); // 只有一个参数省略小括号 Actionstring method2 s Console.WriteLine(s); // 有返回值且只有一行省略 return Funcstring func1 () hello; Funcint, string func2 i i.ToString();二、匿名类型与 var匿名类型的三种接收方式// object无法访问属性编译报错 object model new { Id 1, Name 张三 }; // model.Id // 编译错误 // dynamic运行时检查可以访问但没有编译期类型安全 dynamic dModel new { Id 1, Name 张三 }; Console.WriteLine(dModel.Id); // 运行时才检查 // var编译器推导类型推荐用法 var model new { Id 1, Name 张三 }; Console.WriteLine(model.Id); // 编译期就能检查var 的特点必须在声明时初始化编译器根据右侧值推导类型不能赋值为 null(无法推导类型)不能作为方法参数或返回值类型匿名类型的属性是只读的不能修改var i 13; // 推导为 int var s Richard; // 推导为 string // var x null; // 编译错误三、扩展方法扩展方法允许在不修改原类型源码的情况下为其添加新方法是 LINQ 的实现基础也是遵循开闭原则的常用手段。三要素定义在静态类中方法本身是静态方法第一个参数前加this关键字表示被扩展的类型public static class MethodExtension { // 为 Student 扩展方法 public static void StudyFramework(this Student student) { Console.WriteLine(${student.Id} {student.Name} 学习架构课程); } // 为 int 扩展方法 public static string IntToString(this int i) { return i.ToString(); } // 为 string 扩展方法带默认参数 public static string FormatString(this string oldString, int length 5) { if (string.IsNullOrWhiteSpace(oldString)) return string.Empty; else if (oldString.Length length) return oldString; else return ${oldString.Substring(0, length)}...; } }调用时像实例方法一样使用Student student new Student { Id 1, Name 张三 }; student.StudyFramework(); int num 100; string s num.IntToString(); string text 这是一段很长的文本内容; string result text.FormatString(10); // 这是一段很长的文...(取前10个字符加省略号)注意事项可以扩展任何类型包括密封类和值类型如果类型本身有同名方法优先调用类型自身的方法扩展方法不会覆盖它扩展泛型类型(如this T t)会影响所有类型侵入性强慎用扩展object会影响所有类型不推荐调用方需要引入扩展方法所在的命名空间四、LINQ 基础用法LINQ 有两种等价语法可以混用// 扩展方法语法(Lambda 风格) var list1 studentList.Where(s s.Age 30); // 查询表达式语法(类 SQL 风格) var list2 from s in studentList where s.Age 30 select s;Where — 条件过滤var result studentList.Where(s s.Age 30);Select — 投影将数据转换为新的形状可以组合字段、计算属性var result studentList .Where(s s.Age 30) .Select(s new { IdName s.Id s.Name, ClassName s.ClassId 2 ? 高级班 : 其他班 });OrderBy / ThenBy / OrderByDescending — 排序var result studentList .OrderBy(s s.Id) // 升序 .ThenBy(s s.Name) // 多字段排序(第二优先级) .OrderByDescending(s s.Age); // 降序Skip / Take — 分页必须先排序再分页否则结果不稳定var result studentList .OrderBy(s s.Id) .Skip(10) // 跳过前 10 条 .Take(5); // 取 5 条GroupBy — 分组// 查询表达式 var result from s in studentList where s.Age 30 group s by s.ClassId into sg select new { Key sg.Key, MaxAge sg.Max(t t.Age) }; // 扩展方法等价写法 var result studentList .Where(s s.Age 30) .GroupBy(s s.ClassId) .Select(sg new { Key sg.Key, MaxAge sg.Max(t t.Age) });GroupBy 也支持多字段分组group s by new { s.ClassId, s.Age }Join — 连接查询内连接(只返回两边都匹配的数据)// 查询表达式连接条件必须用 equals不能用 var result from s in studentList join c in classList on s.ClassId equals c.Id select new { s.Name, c.ClassName }; // 扩展方法等价写法 var result studentList.Join( classList, s s.ClassId, c c.Id, (s, c) new { s.Name, c.ClassName } );左连接(左边全部保留右边没有匹配时为 null)var result from s in studentList join c in classList on s.ClassId equals c.Id into scList from sc in scList.DefaultIfEmpty() // 关键DefaultIfEmpty 保留左边无匹配的行 select new { s.Name, ClassName sc null ? 无班级 : sc.ClassName };右连接只需把左右两个数据源交换位置即可。五、LINQ 实现原理从重复代码到 LINQ 的演变思路假设要对集合做多种条件过滤最原始的写法是每种条件写一个方法// 过滤年龄 30 public static ListStudent FilterByAge(ListStudent list) { var result new ListStudent(); foreach (var item in list) if (item.Age 30) result.Add(item); return result; } // 过滤名称长度 2 public static ListStudent FilterByName(ListStudent list) { var result new ListStudent(); foreach (var item in list) if (item.Name.Length 2) result.Add(item); return result; }这两个方法的结构完全一样唯一不同的是判断条件。把不变的逻辑(循环、添加)留在方法里把可变的逻辑(判断条件)通过委托传入就得到了 LINQ 的雏形public static ListT CustomWhereT(this ListT source, FuncT, bool predicate) { var result new ListT(); foreach (var item in source) { if (predicate.Invoke(item)) result.Add(item); } return result; } // 使用 var result studentList.CustomWhere(s s.Age 30);这就是 LINQ 的设计本质不变的逻辑封装在方法内可变的逻辑通过委托传递再用扩展方法提供链式调用用泛型支持任意类型。yield 与延迟执行上面的CustomWhere返回ListT意味着调用时就立即执行了所有判断并把结果全部存入内存。改用yield return配合IEnumerableT返回值可以实现延迟执行public static IEnumerableT CustomWhereIEnumerableT( this IEnumerableT source, FuncT, bool predicate) { foreach (var item in source) { if (predicate.Invoke(item)) yield return item; // 符合条件就立即返回不符合继续往后判断 } }yield return的效果调用方法时不会立即执行方法体只有在外部遍历(foreach)时才会一条一条地执行每次yield return后方法暂停等待下一次迭代再继续这种机制叫做状态机由编译器自动生成IEnumerableStudent query studentList.CustomWhereIEnumerable(s s.Age 30); // 此时方法体尚未执行 foreach (var item in query) { Console.WriteLine(item.Name); // 遍历时才真正执行 }延迟执行的实际影响var query numbers.Where(n n 2); // 未执行 numbers.Add(10); // 修改原始数据 foreach (var item in query) // 此时执行会包含新加的 10 { Console.WriteLine(item); }立即执行的方法(调用后查询立刻完成结果固化)ToList(),ToArray(),ToDictionary()Count(),Sum(),Average(),Min(),Max()First(),FirstOrDefault(),Single(),Any()六、IEnumerable vs IQueryable对比项IEnumerableTIQueryableT命名空间System.Collections.GenericSystem.Linq执行位置内存(客户端)数据源端(如数据库)条件参数类型FuncT, boolExpressionFuncT, bool典型场景LINQ to ObjectsLINQ to SQL / EF性能特点先把数据全部加载到内存再过滤把条件翻译成 SQL在数据库端过滤// IEnumerable全表加载到内存再在内存中过滤 IEnumerableStudent q1 dbContext.Students .AsEnumerable() .Where(s s.Age 20); // IQueryable生成 SQL WHERE Age 20数据库端过滤 IQueryableStudent q2 dbContext.Students .Where(s s.Age 20);七、LINQ to EverythingLINQ 的设计思想不局限于内存集合可以扩展到任何数据源LINQ to Objects查询内存中的对象集合(IEnumerableT)LINQ to SQL查询数据库条件被翻译为 SQL 语句LINQ to XML查询和操作 XML 文档LINQ to EntitiesEntity Framework 的查询接口LINQ to Redis / Cache查询缓存数据源核心思想都一样把固定的处理逻辑封装在内部把可变的查询条件通过委托(或表达式树)传入整合成统一的查询接口。

相关新闻