C#基础–Lambda 和 LINQ
一、Lambda 的前世今生
在.NetFramewok的不同版本都有不同的样子;在.NetCore下也都是支持的;
1.1 .Netframework1.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高级班");
}
1.2 .NetFramework2.0
匿名方法 增加了一个delegate关键字, 可以访问到除了参数以外的局部变量
public delegate void NoReturnWithPara(int x, string y);
int i =0;
NoReturnWithPara method = new NoReturnWithPara(delegate (int id, string name)
{
Console.WriteLine($"{id} {name} 学习.Net高级班");
Console.WriteLine(i); //可以访问到除了参数以外的局部变量 i
});
method.Invoke(123, "Richard");
1.3 .NetFramework3.0 前期
去掉了delegate关键字,添加了一个=> goes to
public delegate void NoReturnWithPara(int x, string y);
int i =0;
NoReturnWithPara method = new NoReturnWithPara((int id, string name) =>
{
Console.WriteLine($"{id} {name} 学习.Net高级班");
Console.WriteLine(i);
});
method.Invoke(123, "Richard");
1.4 .NetFramework3.0 后期
去掉了匿名方法后的参数类型,编译器自动推断来的/编译器提供的便捷功能(语法糖)
public delegate void NoReturnWithPara(int x, string y);
int i =0;
NoReturnWithPara method = new NoReturnWithPara((id, name) => //编译器自动推断来的/编译器提供的便捷功能(语法糖)
{
Console.WriteLine($"{id} {name} 学习.Net高级班");
Console.WriteLine(i);
});
method.Invoke(123, "Richard");
如果匿名方法体中只有一行代码,可以省略方法体的大括号:
public delegate void NoReturnWithPara(int x, string y);
NoReturnWithPara method = new NoReturnWithPara((id, name) => Console.WriteLine($"{id} {name} 学习.Net高级班"));
method.Invoke(123, "Richard");
public delegate void NoReturnWithPara(int x, string y);
NoReturnWithPara method = (id, name) => Console.WriteLine($"{id} {name} 学习.Net高级班");
method.Invoke(123, "Richard");
如果只有一个参数的时候:
Action<string> method = sn => Console.WriteLine($"欢迎{sn} 来到.Net高级班进阶学习");
method.Invoke("牧羊人");
如果有返回值? 如果lambda表达式中只有一行代码,且有返回值,可以省略return;
Func<string> func0 = () => { return "黄大仙" };
//如果有返回值? 如果lambda表达式中只有一行代码,且有返回值,可以省略 `大括号` + `return`;
Func<string> func = () => "黄大仙";
Func<int, string> func1 = i => i.ToString();
大家觉得Lambda表达式本质是什么?
多播委托中可以把Lambda表达式+=
,但是不能把Lambda表达式-=
。因为Lambda表达式其实是一个方法,不同的lambda表达式就是不同的方法。
Lambda的本质是一个方法。
语法糖:编译器提供的便捷功能
二、LINQ
初始化数据
List<Student> studentList = new List<Student>()
{
new Student() { Id=1, Name="赵亮", ClassId=2, Age=35 },
new Student() { Id=2, Name="再努力一点", ClassId=2, Age=23 },
new Student() { Id=3, Name="王炸", ClassId=2, Age=27 },
new Student() { Id=4, Name="疯子科学家", ClassId=2, Age=26 },
new Student() { Id=5, Name="灭", ClassId=2, Age=25 },
new Student() { Id=6, Name="黑骑士", ClassId=2, Age=24 },
new Student() { Id=7, Name="故乡的风", ClassId=2, Age=21 },
new Student() { Id=8, Name="晴天", ClassId=2, Age=22 }
};
2.1 Linq 扩展方法&表达式
var list = studentList.Where<Student>(s => s.Age < 30); //list里面必然是符合要求的数据;
var list = from s in studentList
where s.Age < 30
select s; //list里面必然是符合要求的数据;
以上两种都是LINQ。
2.2 linq to object
Linq – Linq to object:就是针对IEnumerable类型数据
1. IEnumerable 类型数据:可以理解为内存中的数据;其实就是把不变的逻辑封装起来,把可变的逻辑封装成委托来传递;
2. IQueryable 类型数据:可以理解成内存数据—来自于数据库的数据;
延伸:
Linq to Sql:就是把打开数据库连接,查询数据(不变的的逻辑),把sql 的拼装(可变的逻辑)
Linq to Xml:把数据解析这类动作封装起来(不变的逻辑), 把数据筛选条件封装成委托来传递(可变的逻辑)
Linq to Redis:把固定的逻辑封装起来,把不变的逻辑封装成委托传递
Linq to Cache:…
Linq to JSON:…
Linq to Everything:…
IQueryable 和 IEnumerable 存在本质的区别:
- 使用IQueryable 查询
List<Student> studentList = this.GetStudentList();
var query = studentList.AsQueryable();
query.Where(a => a.Age < 30);
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
- 使用 IEnumerable 查询
List<Student> studentList = this.GetStudentList();
var query = studentList.AsEnumerable();
query.Where(a => a.Age < 30);
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
IEnumerable 传递的是一个委托,而 IQueryable 传递的是一个表达式目录树(数据结构)
现在大家觉得Linq 是什么?
Linq 是一种封装思想,对于用户来说,就把不需要关心内部怎么实现,只需要Linq一下即可;微软之前很推崇这种思想,只让开发者关注自己的核心逻辑,不需要关注内部逻辑,彻底的把开发者变成编程小白;如果大家有经验的话,你会发现像Asp .Net MVC就属于傻瓜式开发,成套的;我们不用去关心原理就可以直接上手。
但是现在又变了,现在有更高的要求,像是Linq to JSON、Linq to Redis等并没有人来封装这些。如果大家有兴趣,可以去尝试封装一下。
2.3 基本查询
var list = studentList.Where<Student>(s => s.Age < 30)
.Select(s => new
{
IdName = s.Id + s.Name,
ClassName = s.ClassId == 2 ? "高级班" : "其他班"
});
var list = from s in studentList
where s.Age < 30
select new
{
IdName = s.Id + s.Name,
ClassName = s.ClassId == 2 ? "高级班" : "其他班"
};
Select 方法:
public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);
**备注:**使用 studentList.Where(s => s.Age < 30) 查询并返回数据后,实际内容还是一个 IEnumerable 的数据。但是接着再使用了Select 做投影的话,里面创建了一个匿名对象,返回的也是一个匿名对象。
2.4 分页
var list = studentList.Where<Student>(s => s.Age < 30)//条件过滤
.Select(s => new//投影
{
Id = s.Id,
ClassId = s.ClassId,
IdName = s.Id + s.Name,
ClassName = s.ClassId == 2 ? "高级班" : "其他班"
})
.OrderBy(s => s.Id)//排序
.ThenBy(s=>s.ClassName) //多重排序,可以多个字段排序都生效
.OrderByDescending(s => s.ClassId)//倒排
.Skip(2)//跳过几条
.Take(3)//获取几条
;
性能怎样?=> 本质其实都是循环;
2.5 分组
var list = 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 list = studentList.Where(a=>a.Age<30)
.GroupBy(c => c.ClassId)
.Select(sg => new
{
key = sg.Key,
maxAge = sg.Max(t => t.Age)
});
2.6 内连接 – inner join
var list = from s in studentList
join c in classList on s.ClassId equals c.Id
select new
{
Name = s.Name,
CalssName = c.ClassName
};
var list = studentList.Join(classList, s => s.ClassId, c => c.Id, (s, c) => new
{
Name = s.Name,
CalssName = c.ClassName
});
2.7 左连接 – left join
var list = from s in studentList
join c in classList on s.ClassId equals c.Id
into scList
from sc in scList.DefaultIfEmpty()
select new
{
Name = s.Name,
CalssName = sc == null ? "无班级" : sc.ClassName//c变sc,为空则用
};
var list = studentList.Join(classList, s => s.ClassId, c => c.Id, (s, c) => new
{
Name = s.Name,
CalssName = c.ClassName
}).DefaultIfEmpty();
三、自定义封装
3.1 针对一个具体类的封装
/// <summary>
/// 如果换个条件怎么办?
/// 使用委托:委托可以把方法当做参数传递;方法其实是逻辑,委托可以把逻辑当做参数传递;
/// 委托:应该是返回值为bool的委托,参数是一个Student
/// </summary>
/// <param name="resource"></param>
/// <returns></returns>
public static List<Student> RichardWhere(this List<Student> resource, Func<Student, bool> func)
{
List<Student> list = new List<Student>();
foreach (var item in resource)
{
//if (item.Name.Length > 2) //条件的判断其实就是逻辑---动作 //唯一的区别就在与中间这里的条件不一样;
if (func.Invoke(item))
{
list.Add(item);
}
}
return list;
}
3.2 通用–泛型封装
/// <summary>
/// 这就是Linq中where的本质;
/// 1. 是把固定不变的逻辑,封装起来,把可变的逻辑封装成委托来传递
/// 就可以让开发者只需要关注自己的核心业务,其他别的都以LINQ封装
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="resource"></param>
/// <param name="func"></param>
/// <returns></returns>
public static List<T> RichardWhere<T>(this List<T> resource, Func<T, bool> func) where T:class
{
List<T> list = new List<T>();
foreach (var item in resource)
{
//if (item.Name.Length > 2) //条件的判断其实就是逻辑---动作 //唯一的区别就在与中间这里的条件不一样;
if (func.Invoke(item))
{
list.Add(item);
}
}
return list;
}
/// <summary>
/// 有什么好处?
/// 通过接口来扩展,只要实现了这个接口的,都可以使用当前这个扩展方法
/// 相比而言:自然扩展抽象要好一些,扩展性更好
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="resource"></param>
/// <param name="func"></param>
/// <returns></returns>
public static IEnumerable<T> RichardWhereEnumerable<T>(this IEnumerable<T> resource, Func<T, bool> func) where T : class
{
List<T> list = new List<T>();
foreach (var item in resource)
{
//if (item.Name.Length > 2) //条件的判断其实就是逻辑---动作 //唯一的区别就在与中间这里的条件不一样;
if (func.Invoke(item))
{
list.Add(item);
}
}
return list;
}
3.3 实际运用
如果是学生类的实现对象的话,3.1 和3.2都是的话下面代码一样,无需任何修改
var list1 = MethodExtension.RichardWhere(studentList, item => item.Age < 30);
var list2 = studentList.RichardWhere(item => item.Age < 30);
var list1 = MethodExtension.RichardWhere(studentList, item => item.Name.Length > 2);
var list2 = studentList.RichardWhere(item => item.Name.Length > 2);
var list1 = MethodExtension.RichardWhere(studentList, item => item.Id > 1
&& item.Name != null
&& item.ClassId == 1
&& item.Age > 20);
//循环完毕以后,list里面必然是符合要求的数据;
var list2 = studentList.RichardWhere<Student>(item => item.Id > 1
&& item.Name != null
&& item.ClassId == 1
&& item.Age > 20);
四、yield 使用
/// <summary>
/// yield` 必须和 IEnumerable<T> 配合使用,可以做到按需获取
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="resource"></param>
/// <param name="func"></param>
/// <returns></returns>
public static IEnumerable<T> RichardWhereIterator<T>(this IEnumerable<T> resource, Func<T, bool> func) where T : class
{
foreach (var item in resource)
{
if (func.Invoke(item))
{
yield return item;
}
}
return list;
}
var list3 = studentList.RichardWhereIterator(item => item.Age < 30);
foreach (var item in list3)
{
Console.WriteLine("Name={0} Age={1}", item.Name, item.Age);
}
yield
必须和 IEnumerable<T>
配合使用,可以做到按需获取;如果返回值换成 List<T>
就会产生报错
按需获取: var list3 = studentList.RichardWhereIterator(item => item.Age < 30);
执行完之后,并没有实质性执行到 RichardWhereIterator
方法内部,直到 foreach (var item in list3)
的时候,才执行到方法体内,而且是循环一次就到 RichardWhereIterator
获取一条数据。
使用yield 的使用,编译器会生成一个泛型的,会有个迭代状态机 [IteratorStateMachine] (属于特性)