WPF 中常用的 LINQ 语句
WPF 中常用的 LINQ 语句
在 WPF 开发中,LINQ(Language Integrated Query)主要用于处理集合数据、实现动态过滤、排序,以及将非标准数据源(如 XML 或数据库查询结果)转换为 UI 可绑定的格式。本文汇总常用 LINQ 语句的含义对照表,并给出在 WPF / MVVM 场景下的使用示例。
一、LINQ 语句含义对照表
| LINQ 语句/方法 | 核心含义 | WPF 典型应用场景 |
|---|---|---|
| Where | 筛选集合中满足指定条件的元素,返回过滤后的序列 | 实现搜索框实时过滤列表、DataGrid 条件筛选、主从列表联动 |
| Select | 将集合中的每个元素投影转换为新的形式,支持生成匿名类型或自定义对象 | 从复杂实体中提取 UI 所需字段,生成适配绑定的视图模型集合 |
| OrderBy / OrderByDescending | 对集合元素按指定键进行升序/降序排序 | 为 ListView、DataGrid 提供默认排序逻辑,按日期、价格等字段排列数据 |
| ThenBy / ThenByDescending | 在已有排序基础上,提供第二层级的排序规则 | 实现多字段复合排序,如先按部门排序、再按员工姓名排序 |
| GroupBy | 根据指定键将集合元素进行分组,返回分组后的序列 | 配合 WPF CollectionViewSource 实现数据分类展示,生成带层级的列表视图 |
| Count | 统计集合中元素的总数量,或满足条件的元素数量 | 在状态栏显示数据总数、统计特定分类下的项目条数 |
| Sum / Average / Max / Min | 对集合元素执行求和、求平均值、取最大/最小值的聚合运算 | 计算订单总金额、商品平均价格、日志最新时间等统计指标 |
| First / FirstOrDefault | 获取序列中的第一个元素,无元素时返回默认值 | 获取列表选中项的第一条数据,避免空引用异常 |
| Any | 判断集合中是否存在至少一个满足条件的元素 | 快速校验集合是否有符合条件的数据,控制 WPF 按钮的启用/禁用状态 |
| Distinct | 去除集合中的重复元素,返回唯一值序列 | 生成下拉列表的无重复选项,提取数据源中的所有唯一分类 |
| ToList() / ToArray() | 将 LINQ 查询结果立即实例化为内存中的 List 或数组集合 | 固化查询结果,避免 WPF 绑定时多次执行延迟查询,提升性能 |
| LINQ to XML 查询 | 针对 XElement / XDocument 执行查询,提取 XML 节点数据 | 直接解析 XML 文件作为 WPF 绑定数据源,无需额外转换中间层 |
二、示例前置:公共数据模型与 ViewModel 骨架
以下所有示例均基于该模型与 ViewModel 展开。
// ---- Model ----
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Department { get; set; }
public decimal Salary { get; set; }
public DateTime HireDate { get; set; }
public string FullName => $"{FirstName} {LastName}";
}
// ---- ViewModel 骨架 ----
public class MainViewModel : INotifyPropertyChanged
{
// 完整数据源(通常由 Service / Repository 提供)
public ObservableCollection AllEmployees { get; }
// 绑定到 UI 的过滤结果
private ObservableCollection _filteredEmployees;
public ObservableCollection FilteredEmployees
{
get => _filteredEmployees;
set { _filteredEmployees = value; OnPropertyChanged(); }
}
public MainViewModel()
{
AllEmployees = new ObservableCollection
{
new Employee { Id = 1, FirstName = "张", LastName = "三", Department = "IT", Salary = 12000, HireDate = new DateTime(2020, 3, 1) },
new Employee { Id = 2, FirstName = "李", LastName = "四", Department = "IT", Salary = 15000, HireDate = new DateTime(2019, 7, 15) },
new Employee { Id = 3, FirstName = "王", LastName = "五", Department = "HR", Salary = 9000, HireDate = new DateTime(2021, 1, 10) },
new Employee { Id = 4, FirstName = "赵", LastName = "六", Department = "Finance", Salary = 11000, HireDate = new DateTime(2022, 5, 20) },
new Employee { Id = 5, FirstName = "孙", LastName = "七", Department = "IT", Salary = 18000, HireDate = new DateTime(2018, 11, 5) }
};
FilteredEmployees = new ObservableCollection(AllEmployees);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
三、各语句使用示例
1. Where — 筛选
含义:筛选集合中满足指定条件的元素,返回过滤后的序列。
// 基础:筛选 IT 部门员工
var itStaff = AllEmployees.Where(e => e.Department == "IT");
// WPF 实时搜索:绑定到 TextBox 的 SearchText 变化时触发
private string _searchText;
public string SearchText
{
get => _searchText;
set
{
_searchText = value;
OnPropertyChanged();
ApplyFilter(); // 属性变化即刷新列表
}
}
private void ApplyFilter()
{
// 多条件组合:关键字 + 部门
var query = AllEmployees.AsEnumerable();
if (!string.IsNullOrWhiteSpace(SearchText))
query = query.Where(e => e.FullName.Contains(SearchText));
FilteredEmployees = new ObservableCollection(query);
}
// 复合条件:薪资高于 10000 且 2020 年后入职
var result = AllEmployees.Where(e => e.Salary > 10000 && e.HireDate.Year > 2020);
2. Select — 投影转换
含义:将集合中每个元素投影转换为新形式,可生成匿名类型或自定义对象。
// 投影为匿名类型,仅取显示所需字段,直接绑定到 ListBox
var displayItems = AllEmployees.Select(e => new
{
DisplayName = e.FullName,
DepartmentName = e.Department,
SalaryText = e.Salary.ToString("C0")
}).ToList();
MyListBox.ItemsSource = displayItems;
// 投影为 ViewModel 对象列表
var vms = AllEmployees.Select(e => new EmployeeViewModel
{
Name = e.FullName,
IsHighlyPaid = e.Salary > 12000
}).ToList();
// 带索引的投影(序号从 1 开始)
var indexed = AllEmployees.Select((e, i) => new
{
No = i + 1,
Name = e.FullName
}).ToList();
3. OrderBy / OrderByDescending — 排序
含义:对集合元素按指定键进行升序 / 降序排序。
// 按薪资降序
var bySalaryDesc = AllEmployees.OrderByDescending(e => e.Salary).ToList();
// 按入职日期升序
var byHireAsc = AllEmployees.OrderBy(e => e.HireDate).ToList();
// 排序后赋给绑定集合(固化,避免延迟执行)
FilteredEmployees = new ObservableCollection(bySalaryDesc);
// DataGrid 点击列头排序时的典型写法
private void SortBy(string propertyName, bool ascending)
{
var sorted = ascending
? AllEmployees.OrderBy(e => GetPropertyValue(e, propertyName))
: AllEmployees.OrderByDescending(e => GetPropertyValue(e, propertyName));
FilteredEmployees = new ObservableCollection(sorted);
}
private object GetPropertyValue(Employee e, string prop)
=> typeof(Employee).GetProperty(prop)?.GetValue(e);
4. ThenBy / ThenByDescending — 二级排序
含义:在已有排序基础上提供第二层级的排序规则。
// 先按部门升序,同部门内再按薪资降序
var multiSort = AllEmployees
.OrderBy(e => e.Department)
.ThenByDescending(e => e.Salary)
.ToList();
// 三级排序:部门 → 薪资 → 姓名
var threeLevel = AllEmployees
.OrderBy(e => e.Department)
.ThenByDescending(e => e.Salary)
.ThenBy(e => e.FullName)
.ToList();
5. GroupBy — 分组
含义:根据指定键将集合元素分组,返回分组后的序列。
// 按部门分组,生成分组视图数据结构
var groupedData = AllEmployees
.GroupBy(e => e.Department)
.Select(g => new
{
Department = g.Key,
Count = g.Count(), // 组内人数
TotalSalary = g.Sum(e => e.Salary), // 组合计薪资
Members = g.OrderByDescending(e => e.Salary).ToList()
})
.ToList();
// 绑定到 DataGrid,需在 XAML 中配置 GroupStyle
// XAML:
//
//
//
//
//
//
//
//
//
//
//
var groupedEmployees = AllEmployees.GroupBy(e => e.Department).ToList();
6. Count — 计数
含义:统计集合元素总数,或满足条件的元素数量。
// 总人数
int total = AllEmployees.Count;
// IT 部门人数
int itCount = AllEmployees.Count(e => e.Department == "IT");
// 状态栏显示
StatusText = $"共 {total} 人,其中 IT 部门 {itCount} 人";
// 绑定到 TextBlock 或按钮可用性判断
public bool HasResults => FilteredEmployees.Any();
7. Sum / Average / Max / Min — 聚合统计
含义:对集合元素执行求和、求平均、最大 / 最小值的聚合运算。
// 薪资合计
decimal totalSalary = AllEmployees.Sum(e => e.Salary);
// 平均薪资
decimal avgSalary = AllEmployees.Average(e => e.Salary);
// 最高薪资与对应员工
decimal maxSalary = AllEmployees.Max(e => e.Salary);
Employee topEarner = AllEmployees.FirstOrDefault(e => e.Salary == maxSalary);
// 最早入职日期
DateTime earliest = AllEmployees.Min(e => e.HireDate);
// 一次性生成统计对象,绑定到面板
var stats = new
{
Total = totalSalary,
Average = avgSalary,
Max = maxSalary,
Min = AllEmployees.Min(e => e.Salary)
};
StatsPanel.DataContext = stats;
8. First / FirstOrDefault — 取首元素
含义:获取序列第一个元素,无元素时返回默认值(避免异常)。
// 安全获取第一个员工(无数据时返回 null,不抛异常)
Employee first = AllEmployees.FirstOrDefault();
// 获取薪资最高的员工
Employee richest = AllEmployees.OrderByDescending(e => e.Salary).FirstOrDefault();
// 从选中项取数据(WPF 中常用)
Employee selected = (MyListView.SelectedItem as Employee);
// 用 SingleOrDefault 期望唯一结果
Employee byId = AllEmployees.SingleOrDefault(e => e.Id == 3);
// 注意:First() 在空集合时会抛 InvalidOperationException,
// 绑定场景建议统一用 FirstOrDefault()。
9. Any — 存在性判断
含义:判断集合中是否存在至少一个满足条件的元素。
// 控制按钮启用/禁用
public bool CanDelete => AllEmployees.Any();
public bool HasHighPaid => AllEmployees.Any(e => e.Salary > 15000);
// 在命令的 CanExecute 中使用
private bool CanSave(object param)
=> FilteredEmployees.Any(); // 有数据才允许保存
// 数据去重校验:判断是否已存在同名员工
bool exists = AllEmployees.Any(e => e.FullName == newFullName);
10. Distinct — 去重
含义:去除集合中的重复元素,返回唯一值序列。
// 提取所有唯一部门,填充下拉列表
var departments = AllEmployees
.Select(e => e.Department)
.Distinct()
.OrderBy(d => d)
.ToList();
DeptComboBox.ItemsSource = departments;
// 对复杂对象去重,需要实现 IEqualityComparer 或借助匿名类型
var uniqueByDept = AllEmployees
.GroupBy(e => e.Department)
.Select(g => g.First())
.ToList();
// 自定义比较器去重示例
public class EmployeeComparer : IEqualityComparer
{
public bool Equals(Employee x, Employee y) => x?.Id == y?.Id;
public int GetHashCode(Employee obj) => obj.Id.GetHashCode();
}
var distinctEmployees = AllEmployees.Distinct(new EmployeeComparer()).ToList();
11. ToList() / ToArray() — 立即执行
含义:将 LINQ 查询结果立即实例化为内存集合,避免延迟执行带来的重复查询。
// ❌ 不推荐:延迟执行,绑定到 UI 后每次枚举都会重新执行查询
MyDataGrid.ItemsSource = AllEmployees.Where(e => e.Salary > 10000);
// ✅ 推荐:立即固化,查询只执行一次
MyDataGrid.ItemsSource = AllEmployees
.Where(e => e.Salary > 10000)
.OrderByDescending(e => e.Salary)
.ToList();
// ObservableCollection 支持 UI 自动更新,绑定首选
FilteredEmployees = new ObservableCollection(
AllEmployees.Where(e => e.Department == "IT").ToList());
// 需要数组时用 ToArray
string[] names = AllEmployees.Select(e => e.FullName).ToArray();
12. LINQ to XML — 解析 XML 数据源
含义:针对 XDocument / XElement 执行查询,提取 XML 节点数据作为绑定源。
// 假设 books.xml 内容包含 节点
XDocument doc = XDocument.Load("books.xml");
// 查询 XML 并投影为绑定对象
var books = doc.Descendants("Book")
.Select(b => new
{
Title = b.Element("Title")?.Value,
Author = b.Element("Author")?.Value,
Price = decimal.TryParse(b.Element("Price")?.Value, out var p) ? p : 0m
})
.Where(b => b.Price > 0) // 过滤有效数据
.OrderBy(b => b.Price)
.ToList();
BookListBox.ItemsSource = books;
// 从内存 XML 字符串查询
XDocument xDoc = XDocument.Parse(xmlString);
var nodeValues = xDoc.Descendants("Item")
.Select(x => x.Attribute("Name")?.Value)
.ToList();
四、MVVM 中的实践建议
在 WPF 的 MVVM 架构中,LINQ 查询通常位于 ViewModel 层,而不是 Code-behind(后端代码)中。
- 实时搜索优化:为实现流畅的“智能搜索”体验(如 ComboBox 实时过滤),建议在 ViewModel 中监听搜索属性的变化(使用
UpdateSourceTrigger=PropertyChanged),并在属性 setter 中执行 LINQ 查询更新绑定集合。 - 延迟执行 vs 立即执行:
- LINQ 查询默认是延迟执行的。如果直接将
IQueryable或IEnumerable绑定到 WPF 控件,可能会在 UI 枚举数据时多次执行查询,影响性能。 - 最佳实践:在赋值给
ItemsSource之前,调用.ToList()或.ToArray()将结果实例化(materialize)为内存集合,确保查询只执行一次。
- LINQ 查询默认是延迟执行的。如果直接将
五、总结
在 WPF 中,LINQ 的核心价值在于声明式数据处理,让开发者专注于“需要什么数据”,而不是“如何遍历数据”。常用的组合模式为:
- Where 进行过滤。
- OrderBy 进行排序。
- Select 进行形状转换。
- ToList / ToArray 固化结果以便绑定。
这种链式调用不仅减少了代码量,还显著提升了代码的可读性和可维护性。
原文地址: https://www.cveoy.top/t/topic/qHML 著作权归作者所有。请勿转载和采集!