-
LINQ to Objects——C#编写员工数据筛选与统计
第一部分:C#基础入门
5.LINQ to Objects——员工数据筛选与统计
实例介绍
LINQ to Objects 是.NET中针对内存集合(如List
在实际开发中,员工数据处理是典型场景:比如从员工列表中筛选特定部门的高工资员工、按部门统计人数、计算平均工资等。本实例将通过LINQ to Objects实现这些功能,带你掌握LINQ的核心操作,理解其“声明式编程”的本质(只描述“要什么”,不关心“怎么实现”)。
需求分析
1.定义员工模型:Employee类包含以下属性:
1.Id(员工ID,int);
2.Name(姓名,string);
3.Department(部门,string,如“技术部”“人事部”);
4.Salary(月薪,decimal);
5.HireDate(入职时间,DateTime);
2.核心功能:
1.筛选:找出“技术部”且工资>8000的员工;
2.排序:按工资降序排列员工;
3.统计:计算所有员工的平均工资、最高工资;
4.分组:按部门分组,统计每个部门的人数和平均工资;
5.查找:找出入职时间最早的员工;
3.代码要求:同时展示LINQ查询语法(类似SQL)和方法语法(方法链),对比两种风格的差异;
4.异常处理:处理空集合、无效数据(如工资为负数)的情况。
代码实现
csharp
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqToObjectsDemo
{
// ==================== 1. 员工模型类 ====================
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Department { get; set; } = string.Empty;
public decimal Salary { get; set; }
public DateTime HireDate { get; set; }
}
// ==================== 2. 测试程序 ====================
class Program
{
static void Main(string[] args)
{
// 步骤1:创建测试员工数据
List<Employee> employees = new List<Employee>
{
new() { Id = 1, Name = "张三", Department = "技术部", Salary = 9500, HireDate = new DateTime(2020, 5, 1) },
new() { Id = 2, Name = "李四", Department = "技术部", Salary = 8200, HireDate = new DateTime(2021, 3, 15) },
new() { Id = 3, Name = "王五", Department = "人事部", Salary = 6800, HireDate = new DateTime(2019, 10, 8) },
new() { Id = 4, Name = "赵六", Department = "技术部", Salary = 7500, HireDate = new DateTime(2022, 1, 20) },
new() { Id = 5, Name = "孙七", Department = "销售部", Salary = 10200, HireDate = new DateTime(2018, 7, 30) },
new() { Id = 6, Name = "周八", Department = "销售部", Salary = 8800, HireDate = new DateTime(2021, 9, 1) }
};
// 步骤2:LINQ操作演示
Console.WriteLine("=== 1. 筛选技术部工资>8000的员工(查询语法) ===");
var highSalaryTechEmployees = from emp in employees
where emp.Department == "技术部" && emp.Salary > 8000
select emp;
PrintEmployees(highSalaryTechEmployees);
Console.WriteLine("
=== 2. 按工资降序排序(方法语法) ===");
var sortedBySalary = employees.OrderByDescending(emp => emp.Salary);
PrintEmployees(sortedBySalary);
Console.WriteLine("
=== 3. 统计所有员工平均工资 ===");
decimal avgSalary = employees.Average(emp => emp.Salary);
Console.WriteLine($"平均工资:{avgSalary:C}"); // C表示货币格式
Console.WriteLine("
=== 4. 按部门分组统计(人数+平均工资) ===");
var deptGroup = from emp in employees
group emp by emp.Department into deptGroup
select new
{
Department = deptGroup.Key,
Count = deptGroup.Count(),
AvgSalary = deptGroup.Average(emp => emp.Salary)
};
foreach (var group in deptGroup)
{
Console.WriteLine($"部门:{group.Department} | 人数:{group.Count} | 平均工资:{group.AvgSalary:C}");
}
Console.WriteLine("
=== 5. 查找入职时间最早的员工 ===");
Employee earliestHire = employees.OrderBy(emp => emp.HireDate).FirstOrDefault()!;
Console.WriteLine($"最早入职:{earliestHire.Name} | 入职时间:{earliestHire.HireDate:yyyy-MM-dd}");
}
// 辅助方法:打印员工列表
static void PrintEmployees(IEnumerable<Employee> employees)
{
foreach (var emp in employees)
{
Console.WriteLine($"ID:{emp.Id} | 姓名:{emp.Name} | 部门:{emp.Department} | 工资:{emp.Salary:C}");
}
}
}
}
逐行讲解
-
员工模型类(Employee)
csharp
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Department { get; set; } = string.Empty;
public decimal Salary { get; set; }
public DateTime HireDate { get; set; }
}
包含员工核心属性,string类型初始化= string.Empty避免空引用;
decimal用于工资(避免浮点精度丢失),DateTime用于入职时间。
2. 测试数据创建
csharp
List<Employee> employees = new List<Employee>
{
new() { Id =1, Name="张三", Department="技术部", Salary=9500, HireDate=new DateTime(2020,5,1) },
// ...其他员工
};
用对象初始化器快速创建员工列表,替代传统的new Employee(); emp.Id=1;写法。
3. LINQ操作详解
(1)筛选员工(查询语法)
csharp
var highSalaryTechEmployees = from emp in employees
where emp.Department == "技术部" && emp.Salary >8000
select emp;
from emp in employees:指定查询的数据源(员工列表),emp是集合中每个元素的别名;
where:筛选条件(部门为技术部且工资>8000);
select emp:指定返回结果(完整的员工对象);
延迟执行:此时LINQ并未真正执行查询,直到调用PrintEmployees(枚举集合)时才会计算结果。
(2)按工资降序排序(方法语法)
csharp
var sortedBySalary = employees.OrderByDescending(emp => emp.Salary);
OrderByDescending:LINQ扩展方法,接受一个Lambda表达式(emp => emp.Salary)作为排序键;
方法链:可以继续追加操作,比如employees.OrderByDescending(...).ThenBy(emp=>emp.Name)(工资相同则按姓名排序)。
(3)统计平均工资
csharp
decimal avgSalary = employees.Average(emp => emp.Salary);
Average:聚合函数,计算指定属性的平均值;
立即执行:调用Average时会立即遍历集合,返回结果(区别于延迟执行的查询)。
(4)按部门分组统计
csharp
var deptGroup = from emp in employees
group emp by emp.Department into deptGroup
select new
{
Department = deptGroup.Key,
Count = deptGroup.Count(),
AvgSalary = deptGroup.Average(emp => emp.Salary)
};
group emp by emp.Department:按部门分组,deptGroup是分组后的结果(类型为IGrouping<string, Employee>);
into deptGroup:将分组结果重命名为deptGroup,以便后续操作;
匿名类型:select new { ... }创建一个临时对象,包含部门名称、人数、平均工资(无需定义实体类)。
(5)查找最早入职员工
csharp
Employee earliestHire = employees.OrderBy(emp => emp.HireDate).FirstOrDefault()!;
OrderBy:按入职时间升序排序;
FirstOrDefault:返回排序后的第一个元素(入职最早),若集合为空则返回null(!表示非空断言,确保结果不为空)。
基础知识拓展
-
LINQ核心概念
概念 说明
延迟执行 LINQ查询在枚举集合(如foreach、ToList)时才执行,节省内存(无需提前存储所有结果);
立即执行 调用聚合函数(Average、Count、Sum)或ToList()/ToArray()时,立即执行查询并返回结果;
查询语法 vs 方法语法 查询语法更接近SQL,易读;方法语法更灵活,支持链式调用;两者可以混合使用(比如from...where...select+OrderBy);
Lambda表达式 LINQ方法语法的核心,用于传递筛选条件、排序键等(格式:(参数) => 表达式); - 常见LINQ操作符分类
| 分类 | 示例方法/关键字 | 作用 |
|---|---|---|
| 筛选 | Where、OfType | 从集合中筛选符合条件的元素; |
| 排序 | OrderBy、OrderByDescending、ThenBy | 对集合进行排序; |
| 投影 | Select、SelectMany | 转换集合元素(比如只返回员工姓名:employees.Select(emp=>emp.Name)); |
| 聚合 | Average、Count、Sum、Max | 计算集合的统计值; |
| 分组 | group by、GroupBy | 按指定键分组; |
| 连接 | Join、GroupJoin | 类似SQL的JOIN操作(比如员工表与部门表连接); |
-
LINQ vs 传统循环
以“筛选技术部高工资员工”为例,对比两种写法:
传统循环:
csharp
List<Employee> result = new List<Employee>();
foreach (var emp in employees)
{
if (emp.Department == "技术部" && emp.Salary >8000)
{
result.Add(emp);
}
}
LINQ:
csharp
var result = employees.Where(emp => emp.Department == "技术部" && emp.Salary>8000);
优势:LINQ代码更简洁,无需手动管理临时集合,且可读性更高(一眼就能看出要筛选什么)。
扩展思考
1.处理空集合:如果员工列表为空,Average会抛出InvalidOperationException,如何避免?
用employees.DefaultIfEmpty().Average(...)(空集合时返回0);
或先判断employees.Any()(是否有元素)再执行聚合操作。
2.自定义排序:如何按“姓名长度”排序?
oemployees.OrderBy(emp => emp.Name.Length);
3.复杂筛选:如何筛选“入职时间在2020年之后且工资>9000”的员工?
查询语法:where emp.HireDate.Year >=2020 && emp.Salary>9000;
方法语法:employees.Where(emp => emp.HireDate.Year>=2020 && emp.Salary>9000);
4.性能优化:对于大数据量集合,LINQ的延迟执行会多次枚举吗?
会!如果多次使用同一个LINQ查询(比如highSalaryTechEmployees),会多次遍历集合;
优化方案:用ToList()将结果缓存到内存(var result = highSalaryTechEmployees.ToList())。
总结
LINQ to Objects是.NET开发者的必备技能,它彻底改变了集合数据的处理方式——从“命令式编程”(告诉计算机“怎么做”)转向“声明式编程”(告诉计算机“要什么”)。通过本实例的员工数据处理,你应该掌握了LINQ的核心操作:筛选、排序、聚合、分组,以及两种语法的使用场景。
在实际开发中,LINQ可以大幅减少代码量,提高可读性和可维护性。建议多尝试用LINQ替代传统循环,逐渐养成“声明式编程”的思维习惯。
本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49417.html










