Linq的基本操作及简单例子(重点Group操作)

参考资料:

bilibili杨中科.NET视频教程

MSDN文档

学习C#时,发现C#有一个很神奇的Linq,虽然学了一些,但一直对其中的分组操作不太熟悉。今天借杨中科老师的例子中的示例数据来学习一下Group,以及结合Group实现的一些操作。杨老师视频中说Linq大多数场景下效率都很不错,看上去是个值得经常使用的东西。

先准备一下测试数据:

声明一个Employee类:

class Employee
{
    public long 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,用来进行一系列操作:

List<Employee> list = new();
list.Add(new Employee() { Id = 1, Name = "jerry", Age = 28, Gender = true, Salary = 5000 });
list.Add(new Employee() { Id = 2, Name = "jim", Age = 33, Gender = true, Salary = 3000 });
list.Add(new Employee() { Id = 3, Name = "lily", Age = 35, Gender = false, Salary = 9000 });
list.Add(new Employee() { Id = 4, Name = "lucy", Age = 16, Gender = false, Salary = 2000 });
list.Add(new Employee() { Id = 5, Name = "kimi", Age = 25, Gender = true, Salary = 1000 });
list.Add(new Employee() { Id = 6, Name = "nancy", Age = 35, Gender = false, Salary = 8000 });
list.Add(new Employee() { Id = 7, Name = "zack", Age = 35, Gender = true, Salary = 8500 });
list.Add(new Employee() { Id = 8, Name = "jack", Age = 33, Gender = true, Salary = 8000 });

先熟悉一下一些基本操作:

Where:

// 1. 获取 Age>30 且 Salary>=5000 的员工
var list1 = list.Where(e => e.Age > 30 && e.Salary >= 5000).ToList();
list1.ForEach(e => Console.WriteLine(e));
// 运行结果
//Id = 3, Name = lily, Age = 35, Gender = False, Salary = 9000
//Id = 6, Name = nancy, Age = 35, Gender = False, Salary = 8000
//Id = 7, Name = zack, Age = 35, Gender = True, Salary = 8500
//Id = 8, Name = jack, Age = 33, Gender = True, Salary = 8000

Count:

// 2. 统计 Age>30 且 Salary>=5000 的员工个数
Console.WriteLine(list.Count(e => e.Age > 30 && e.Salary >= 5000));
// 运行结果:
//4

Any:

// 3. 查询是否有工资大于10000的员工
Console.WriteLine(list.Any(e => e.Salary > 10000));
// 运行结果:
//False

// 4. 查询是否有工资大于5000的员工
Console.WriteLine(list.Any(e => e.Salary > 5000));
// 运行结果:
//True

Single & SingleOrDefault:

// 5. Single
Console.WriteLine(list.Single());
// 运行结果:
//抛异常:System.InvalidOperationException: 'Sequence contains more than one element'

// 6. 有条件的Single
Console.WriteLine(list.Single(e => e.Name == "jerry"));
// 运行结果:
//Id=1, Name=jerry, Age=28, Gender=True, Salary=5000

// 7. SingleOrDefault
Console.WriteLine(list.SingleOrDefault());
// 运行结果:
//抛异常:System.InvalidOperationException: 'Sequence contains more than one element'

// 8. 有条件的SingleOrDefault
Console.WriteLine(list.SingleOrDefault(e => e.Name == "Fuck"));
// 运行结果:
//空白,啥也没有,可能是因为Employee没有写带参数的构造函数,我们没有用构造函数来初始化

OrderBy & OrderByDescending & ThenBy & ThenByDescending :

// 9. 随机排序
var r = new Random();
var list2 = list.OrderBy(e => r.Next()).ToList();
list2.ForEach(e => Console.WriteLine(e));
// 运行结果:
//Id = 8, Name = jack, Age = 33, Gender = True, Salary = 8000
//Id = 2, Name = jim, Age = 33, Gender = True, Salary = 3000
//Id = 7, Name = zack, Age = 35, Gender = True, Salary = 8500
//Id = 1, Name = jerry, Age = 28, Gender = True, Salary = 5000
//Id = 4, Name = lucy, Age = 16, Gender = False, Salary = 2000
//Id = 5, Name = kimi, Age = 25, Gender = True, Salary = 1000
//Id = 6, Name = nancy, Age = 35, Gender = False, Salary = 8000
//Id = 3, Name = lily, Age = 35, Gender = False, Salary = 9000

// 10. 按年龄从小到大排序
var list3 = list.OrderBy(e => e.Age).ToList();
list3.ForEach(e => Console.WriteLine(e));
// 运行结果:
//Id = 4, Name = lucy, Age = 16, Gender = False, Salary = 2000
//Id = 5, Name = kimi, Age = 25, Gender = True, Salary = 1000
//Id = 1, Name = jerry, Age = 28, Gender = True, Salary = 5000
//Id = 2, Name = jim, Age = 33, Gender = True, Salary = 3000
//Id = 8, Name = jack, Age = 33, Gender = True, Salary = 8000
//Id = 3, Name = lily, Age = 35, Gender = False, Salary = 9000
//Id = 6, Name = nancy, Age = 35, Gender = False, Salary = 8000
//Id = 7, Name = zack, Age = 35, Gender = True, Salary = 8500

// 11. 按 Name 的最后一个字母逆序排序
var list4 = list.OrderByDescending(e => e.Name[^1]).ToList(); // e.Name[^1] 相当于 e.Name[e.Name.Length - 1]
list4.ForEach(e => Console.WriteLine(e));
// 运行结果:
//Id = 1, Name = jerry, Age = 28, Gender = True, Salary = 5000
//Id = 3, Name = lily, Age = 35, Gender = False, Salary = 9000
//Id = 4, Name = lucy, Age = 16, Gender = False, Salary = 2000
//Id = 6, Name = nancy, Age = 35, Gender = False, Salary = 8000
//Id = 2, Name = jim, Age = 33, Gender = True, Salary = 3000
//Id = 7, Name = zack, Age = 35, Gender = True, Salary = 8500
//Id = 8, Name = jack, Age = 33, Gender = True, Salary = 8000
//Id = 5, Name = kimi, Age = 25, Gender = True, Salary = 1000

// 12. 先按年龄排序,再按工资逆序排
var list5 = list.OrderBy(e => e.Age).ThenByDescending(e => e.Salary).ToList();
list5.ForEach(e => Console.WriteLine(e));
// 运行结果:
//Id = 4, Name = lucy, Age = 16, Gender = False, Salary = 2000
//Id = 5, Name = kimi, Age = 25, Gender = True, Salary = 1000
//Id = 1, Name = jerry, Age = 28, Gender = True, Salary = 5000
//Id = 8, Name = jack, Age = 33, Gender = True, Salary = 8000
//Id = 2, Name = jim, Age = 33, Gender = True, Salary = 3000
//Id = 3, Name = lily, Age = 35, Gender = False, Salary = 9000
//Id = 7, Name = zack, Age = 35, Gender = True, Salary = 8500
//Id = 6, Name = nancy, Age = 35, Gender = False, Salary = 8000

Skip & Take:

// 13. 跳过前两个,再取三个
var list6 = list.Skip(2).Take(3).ToList();
list6.ForEach(e => Console.WriteLine(e));
// 运行结果:
//Id = 3, Name = lily, Age = 35, Gender = False, Salary = 9000
//Id = 4, Name = lucy, Age = 16, Gender = False, Salary = 2000
//Id = 5, Name = kimi, Age = 25, Gender = True, Salary = 1000

Sum & Average & Max & Min:

// 14. 统计30岁以上员工的工资总数(资本家扶了一下眼镜🧐)
var sumSalary = list.Where(e => e.Age > 30).Sum(e => e.Salary);
Console.WriteLine(sumSalary);
// 运行结果:36500

// 15. 统计30岁以上员工的工资平均数
var avgSalary = list.Where(e => e.Age > 30).Average(e => e.Salary);
Console.WriteLine(avgSalary);
// 运行结果:7300

// 16. 取工资最高的人的工资
var maxSalary = list.Max(e => e.Salary);
Console.WriteLine(maxSalary);
// 运行结果:9000

// 17. 取年龄最小的人的年龄
var minAge = list.Min(e => e.Age);
Console.WriteLine(minAge);
// 运行结果:16

Group:

// 18. 按年龄分组,然后遍历分组
var group1 = list.GroupBy(e => e.Age).ToList();
group1.ForEach(g =>
{
    Console.WriteLine("Key: " + g.Key);
    Console.WriteLine("最大工资:" + g.Max(e => e.Salary));
    foreach (Employee e in g)
    {
        Console.WriteLine(e);
    }
});
// 运行结果:
//Key: 28
//最大工资: 5000
//Id = 1, Name = jerry, Age = 28, Gender = True, Salary = 5000

//Key: 33
//最大工资: 8000
//Id = 2, Name = jim, Age = 33, Gender = True, Salary = 3000
//Id = 8, Name = jack, Age = 33, Gender = True, Salary = 8000

//Key: 35
//最大工资: 9000
//Id = 3, Name = lily, Age = 35, Gender = False, Salary = 9000
//Id = 6, Name = nancy, Age = 35, Gender = False, Salary = 8000
//Id = 7, Name = zack, Age = 35, Gender = True, Salary = 8500

//Key: 16
//最大工资: 2000
//Id = 4, Name = lucy, Age = 16, Gender = False, Salary = 2000

//Key: 25
//最大工资: 1000
//Id = 5, Name = kimi, Age = 25, Gender = True, Salary = 1000

该例子中,分组并且 ToList() 后的 group1 的类型为List<IGrouping<int, Employee>>

如果不 ToList(),类型应该为 IEnumerable<IGrouping<int, Employee>>。int即为被分好的值几个group的键的类型,Employee为该group的值的类型。

可以先对IEnumerable<IGrouping<int, Employee>>进行foreach,得到的是一个个IGrouping<int, Employee> ,然后直接对 IGrouping<int, Employee>进行foreach操作,得到的都是这个组里的Employee。

多条件Group:

// 19. 多条件分组,按照 Age 和 Gender 分组
var group2 = list.GroupBy(e => new { e.Age, e.Gender });
foreach (var g in group2)
{
    Console.WriteLine("Key: " + g.Key);
    Console.WriteLine("最大工资:" + g.Max(e => e.Salary));
    foreach (Employee e in g)
    {
        Console.WriteLine(e);
    }
    Console.WriteLine();
}
// 运行结果:
//Key: { Age = 28, Gender = True }
//最大工资: 5000
//Id = 1, Name = jerry, Age = 28, Gender = True, Salary = 5000

//Key: { Age = 33, Gender = True }
//最大工资: 8000
//Id = 2, Name = jim, Age = 33, Gender = True, Salary = 3000
//Id = 8, Name = jack, Age = 33, Gender = True, Salary = 8000

//Key: { Age = 35, Gender = False }
//最大工资: 9000
//Id = 3, Name = lily, Age = 35, Gender = False, Salary = 9000
//Id = 6, Name = nancy, Age = 35, Gender = False, Salary = 800

//Key: { Age = 16, Gender = False }
//最大工资: 2000
//Id = 4, Name = lucy, Age = 16, Gender = False, Salary = 2000

//Key: { Age = 25, Gender = True }
//最大工资: 1000
//Id = 5, Name = kimi, Age = 25, Gender = True, Salary = 1000

//Key: { Age = 35, Gender = True }
//最大工资: 8500
//Id = 7, Name = zack, Age = 35, Gender = True, Salary = 8500

这里使用了匿名类来进行多条件筛选,可以看到分好组的group2的类型为IEnumerable<IGrouping<'a, Employee>>,即键为'a,而且下面也说明了匿名类型'anew { int Age, bool Gender }:

在例19的group2的基础上进行Where筛选:

// 20. 在例19的group2的基础上进行筛选,筛选Age为35,且Gender为false的员工
var group3 = group2.Where(g => g.Key.Age == 35 && g.Key.Gender == false);
foreach (var g in group3)
{
    foreach (Employee e in g)
    {
        Console.WriteLine(e);
    }
    Console.WriteLine();
}
// 运行结果:
//Id = 3, Name = lily, Age = 35, Gender = False, Salary = 9000
//Id = 6, Name = nancy, Age = 35, Gender = False, Salary = 8000

group3的类型和group2是相同的:

可以看到上面代码中group2.Where中可以直接g.Key.Age,因为g.Key是一个匿名类。而且用g.Key只能取到匿名类中的属性,即Age和Gender。不作为GroupBy的参数的其他属性,如Id和Name,都取不到。这一点跟SQL中的GROUP BY操作非常相似。我之前一直不理解为什么SQL中的那种规定,现在竟然借助Linq理解了一些SQL。

同样,上面代码中foreach一个IGrouping<'a, Employee>,也可以取到每一个分组的每一个Employee。

先分组,然后使用分组的数据进行新数据的构造:

// 21. 先按照Age分组,再取得每个分组的最大工资,最小工资,平均工资,以及分组内的人数
var group3 = list.GroupBy(e => e.Age)
    .Select(g => new
    {
        Age = g.Key,
        MaxSalary = g.Max(e => e.Salary),
        MinSalary = g.Min(e => e.Salary),
        AvgSalary = g.Average(e => e.Salary),
        Count = g.Count()
    });
foreach (var item in group3)
{
    Console.WriteLine(item.Age + "," + item.MaxSalary + "," + item.MinSalary + "," + item.AvgSalary + "," + item.Count);
}
// 运行结果:
//28,5000,5000,5000,1
//33,8000,3000,5500,2
//35,9000,8000,8500,3
//16,2000,2000,2000,1
//25,1000,1000,1000,1

综合运用Where,Group,OrderBy,Select:

// 22. 先筛选年龄不小于20岁的,然后按照年龄分组,分好组之后,各个组之间根据Age排序,即根据组的Key排序,然后选前3个组,构造一个包含年龄,数量,平均工资的数据结构
var list3 = list.Where(e => e.Age >= 20)
    .GroupBy(e => e.Age)
    .OrderBy(g => g.Key)
    .Take(3)
    .Select(g => new { Age = g.Key, Count = g.Count(), AvgSalary = g.Average(e => e.Salary) });
foreach (var item in list3)
{
    Console.WriteLine(item);
}
// 运行结果:
//{ Age = 25, Count = 1, AvgSalary = 1000 }
//{ Age = 28, Count = 1, AvgSalary = 5000 }
//{ Age = 33, Count = 2, AvgSalary = 5500 }

难道分完组之后只能用group的Key进行排序吗?当然不是:

// 23. 与例22条件相似,不过这次先按照Age分组,按照Age排序,再在组内按照工资正序排序
var list4 = list.Where(e => e.Age >= 20)
    .GroupBy(e => e.Age)
    .OrderBy(g => g.Key)
    .Select(g => g.OrderBy(e => e.Salary));
foreach (var l in list4)
{
    foreach (var item in l)
    {
        Console.WriteLine(item);
    }
    Console.WriteLine();
}
// 运行结果:
//Id = 5, Name = kimi, Age = 25, Gender = True, Salary = 1000

//Id = 1, Name = jerry, Age = 28, Gender = True, Salary = 5000

//Id = 2, Name = jim, Age = 33, Gender = True, Salary = 3000
//Id = 8, Name = jack, Age = 33, Gender = True, Salary = 8000

//Id = 6, Name = nancy, Age = 35, Gender = False, Salary = 8000
//Id = 7, Name = zack, Age = 35, Gender = True, Salary = 8500
//Id = 3, Name = lily, Age = 35, Gender = False, Salary = 9000

同时注意一波这里list4的类型:

一些其他的常用操作:

// 24. 有一个由整数(逗号分隔)构成的字符串,取该字符串中各个整数的平均值:
var intArrStr = "53,33,55,888,9";
var avg = intArrStr.Split(",")
    .Select(s => int.Parse(s.Trim()))
    .Average();
Console.WriteLine(avg);
// 运行结果:207.6

综合运用举例:

// 25. 将一个大小写英文字母,数字,符号混合组成的字符串,统计其中字母出现的频率,取出现两次以上的,并根据频率从大到小排序
var str = "hFfm3hsS34xgd2rxa1@32Ae%hV$$Ci^v5*yW(neur53x)HKmy@eh!fx5#i4HdySgFfzyudgfvi86sdMzlzh";

var freStat = str.Where(c => char.IsLetter(c))
    .Select(c => char.ToLower(c))
    .GroupBy(l => l)
    .OrderByDescending(g => g.Count())
    .Where(g => g.Count() > 2)
    .Select(g => new { Letter = g.Key, Frequency = g.Count() });

foreach (var item in freStat)
{
    Console.WriteLine($"letter:{item.Letter}, frequency:{item.Frequency}");
}
// 运行结果:
//letter: h, frequency: 7
//letter: f, frequency: 6
//letter: s, frequency: 4
//letter: x, frequency: 4
//letter: d, frequency: 4
//letter: y, frequency: 4
//letter: m, frequency: 3
//letter: g, frequency: 3
//letter: e, frequency: 3
//letter: v, frequency: 3
//letter: i, frequency: 3
//letter: z, frequency: 3
posted @ 2021-06-01 09:06  Kit_L  阅读(508)  评论(0编辑  收藏  举报