LINQ学习记录

发布时间 2023-07-24 17:57:34作者: luffii

一、复习委托

委托是方法的类型,调用委托变量执行的是变量指向的方法

e.g int i = 5(int 类型的变量i指向5)

        public delegate void D1();
        static void Main(string[] args)
        {

            D1 d = F2;

        }
        static void F2()
        {

        }

 .net定义了泛型委托Action(无返回值)和Func(有返回值)

        static void Main(string[] args)
        {
            Func<int, int, int> a1 = Add;
            Console.WriteLine(a1.Invoke(1, 2));
            Action<string> s1 = SayHi;
            s1.Invoke("tom");

        }
        static int Add(int n1,int n2)
        {
            return n1 + n2;
        }
        static void SayHi(string name)
        {
            Console.WriteLine("hello " + name);
        }

二、复习Lambda

  • 委托可以指向匿名方法delegate(){}
  • Lambda将delegate和类型名省略,加上=>
  • 如果=>方法只有一行,且方法有返回值,可以省略{}和return
  • 如果参数只有一个,可以省略()
  • 编译时通过委托类型推断参数类型

三、LINQ

关键功能:提供集合类的扩展方法,即所有实现IEnumerable<T>接口的类都可以使用LINQ,这些方法以拓展方法形式存在LINQ的静态类中。

//数据准备
Id=1,Name=Jerry,Age=23,Gender=True,Salary=5000
Id=2,Name=Tom,Age=25,Gender=False,Salary=4000
Id=3,Name=Lily,Age=22,Gender=False,Salary=3000
Id=4,Name=Jason,Age=22,Gender=True,Salary=7000
Id=5,Name=nancy,Age=24,Gender=False,Salary=3400
Id=6,Name=Zack,Age=18,Gender=True,Salary=5000
Id=7,Name=Moly,Age=33,Gender=True,Salary=1000
Id=8,Name=IMD,Age=24,Gender=True,Salary=11000
class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public bool Gender { get; set; }
    public int Salary { get; set; }

    public override string ToString()
    {
        return $"Id={Id},Name={Name},Age={Age},Gender={Gender},Salary={Salary}";
    }
}            
List<Employee> list = new List<Employee>();

常用扩展方法:

  • 数据过滤Where

IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source,Func<Tsource,bool>predicate)

参数是一个(参数为元素类型,返回值为bool的委托),Tsource集合的每一个元素都会经过predicate的测试,如果predicat测试返回值为true,则将结果放回Where返回值中,Where的返回值为通过predicate条件测试的集合

//工资高于2500,年龄小于250
IEnumerable<Employee> list1 = list.Where(e => e.Age > 20 && e.Salary > 2500);
  • 获取数据条数(数据过多调用LongCount)
//工资高于3000,年龄小于23的员工个数
list.Count(e => e.Salary > 3000 && e.Age > 23);
list.Where(e => e.Salary > 3000 && e.Age > 23).Count();
  • 判断是否至少有一条满足的数据
//判断是否存在工资高于2000的员工
list.Any(e => e.Salary > 2000);

*用count()>0也是一样的,区别在于Any只关心“有没有符合的数据”,不关心有几条数据,因此只要遇到一个满足条件Any就停止了

  • 获取一条数据
    • Single【有且只有一条数据,有返回,没有\超过一条满足抛出异常】
    • SingleOrDefault【有且只有一条数据,有返回,没有返回默认值,超过一条满足抛出异常】
    • First【有一条或多条,返回第一条,没有抛出异常】
    • FirstOrDefault【有一条或多条,返回第一条,没有返回默认值】
  • 排序
//按年龄进行倒序排序
IOrderedEnumerable<Employee>  list1 = list.OrderByDescending(e => e.Age);
foreach(Employee e in list1)
{
    Console.WriteLine(e.ToString());
}
  • 限制结果集
//跳过第二条,从第三条开始取3条数据
IEnumerable<Employee> list1 = list.Skip(2).Take(3);
foreach(Employee e in list1)
{
    Console.WriteLine(e.ToString());
}
  • 聚合函数
    • Sum
    • Min
    • Count
    • Avg 
  • 分组(重点)

IEnumerable<IGrouping<TKey,TSource>> GroupBy<TSource,TKey>(this IEnumerable<TSource> source,Func<TSource,TKey> keySelector);

keySelector是分组条件表达式,GroupBy的返回值是IGrouping<TKey,TSource>类型的泛型IEnumerable,其中IGrouping唯一的成员是Key属性,表示这一组的分组数据项,后面跟着TSource即一个集合(<TKey,TSource>),但注意区分Tkey代表分组表达式的类型,Key是IGrouping这个接口的成员。

//按年龄分组,计算组内的人数、平均工资
IEnumerable<IGrouping<int, Employee>> list1 = list.GroupBy(e => e.Age);
foreach(IGrouping<int,Employee> a in list1)
{
    Console.WriteLine(a.Key);//每一个a元素都是含有一个key和TSource的IGrouping接口,它集成了IEnumerable所以可以直接使用LINQ
    Console.WriteLine("人数:"+a.Count());
    Console.WriteLine("平均工资:"+a.Average(d=>d.Salary));
    foreach(Employee e in a)
    {
        Console.WriteLine(e.ToString());
    }
}

//按性别分组,统计各个分组的人数、平均工资和最小年龄
IEnumerable<IGrouping<bool, Employee>> list1 = list.GroupBy(e => e.Gender);
foreach(IGrouping<bool,Employee> a in list1)
{
    Console.WriteLine(a.Key?"男":"女");
    Console.WriteLine("人数:"+a.Count());
    Console.WriteLine("平均工资:"+a.Average(d=>d.Salary));
    Console.WriteLine("最小年龄:"+a.Min(d=>d.Age));
    foreach(Employee e in a)
    {
        Console.WriteLine(e.ToString());
    }
}
/*输出2:
男
人数:5
平均工资:5800
最小年龄:18
Id=1,Name=Jerry,Age=23,Gender=True,Salary=5000
Id=4,Name=Jason,Age=22,Gender=True,Salary=7000
Id=6,Name=Zack,Age=18,Gender=True,Salary=5000
Id=7,Name=Moly,Age=33,Gender=True,Salary=1000
Id=8,Name=IMD,Age=24,Gender=True,Salary=11000
女
人数:3
平均工资:3466.6666666666665
最小年龄:22
Id=2,Name=Tom,Age=25,Gender=False,Salary=4000
Id=3,Name=Lily,Age=22,Gender=False,Salary=3000
Id=5,Name=nancy,Age=24,Gender=False,Salary=3400*/
  • 投影Select

把集合中的每一项转换为另外一项,甚至可以转换类型(发挥想象力~~~·)

//将Gender转换为字符串
IEnumerable<string> genders = list.Select(e => e.Gender ? "男" : "女");

*匿名类型

没有名字的类,不能被引用,只能在创建的时候用new声明

通常在LINQ中用在Select语句中

//提取Name和Age,将Gender转换为字符串
var list1 = list.Select(e => new { e.Name, e.Age, Xingbie = e.Gender ? "男" : "女" });
foreach(var i in list1)
{
    Console.WriteLine($"{i.Name} {i.Age} {i.Xingbie}");
}
  • 集合转换
    • 转数组 
    • 转List<T>
Employee[] list1 = list.Where(e => e.Salary > 3000).ToArray();
List<Employee> list2 = list.Where(e => e.Salary > 3000).ToList();
  • 综合练习
//获取Id>2的数据,再按照Age分组,并且把分组按照Age排序,然后取出前3条
//最后投影取得年龄、人数、平均工资
var list1 = list.Where(e => e.Id > 2).GroupBy(e => e.Age).OrderBy(g => g.Key).Take(3)
    .Select(e => new {Age = e.Key, Renshu = e.Count(), AvgSalary = e.Average(d => d.Salary) });
foreach(var i in list1)
{
    Console.WriteLine($"年龄:{i.Age} 人数:{i.Renshu} 平均工资:{i.AvgSalary}");
}