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 展开。

csharp 复制代码
// ---- 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<Employee> AllEmployees { get; }

    // 绑定到 UI 的过滤结果
    private ObservableCollection<Employee> _filteredEmployees;
    public ObservableCollection<Employee> FilteredEmployees
    {
        get => _filteredEmployees;
        set { _filteredEmployees = value; OnPropertyChanged(); }
    }

    public MainViewModel()
    {
        AllEmployees = new ObservableCollection<Employee>
        {
            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<Employee>(AllEmployees);
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string name = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

三、各语句使用示例

1. Where --- 筛选

含义:筛选集合中满足指定条件的元素,返回过滤后的序列。

csharp 复制代码
// 基础:筛选 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<Employee>(query);
}

// 复合条件:薪资高于 10000 且 2020 年后入职
var result = AllEmployees.Where(e => e.Salary > 10000 && e.HireDate.Year > 2020);

2. Select --- 投影转换

含义:将集合中每个元素投影转换为新形式,可生成匿名类型或自定义对象。

csharp 复制代码
// 投影为匿名类型,仅取显示所需字段,直接绑定到 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 --- 排序

含义:对集合元素按指定键进行升序 / 降序排序。

csharp 复制代码
// 按薪资降序
var bySalaryDesc = AllEmployees.OrderByDescending(e => e.Salary).ToList();

// 按入职日期升序
var byHireAsc = AllEmployees.OrderBy(e => e.HireDate).ToList();

// 排序后赋给绑定集合(固化,避免延迟执行)
FilteredEmployees = new ObservableCollection<Employee>(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<Employee>(sorted);
}

private object GetPropertyValue(Employee e, string prop)
    => typeof(Employee).GetProperty(prop)?.GetValue(e);

4. ThenBy / ThenByDescending --- 二级排序

含义:在已有排序基础上提供第二层级的排序规则。

csharp 复制代码
// 先按部门升序,同部门内再按薪资降序
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 --- 分组

含义:根据指定键将集合元素分组,返回分组后的序列。

csharp 复制代码
// 按部门分组,生成分组视图数据结构
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:
// <DataGrid ItemsSource="{Binding GroupedEmployees}">
//     <DataGrid.GroupStyle>
//         <GroupStyle>
//             <GroupStyle.HeaderTemplate>
//                 <DataTemplate>
//                     <TextBlock Text="{Binding Name}" FontWeight="Bold"/>
//                 </DataTemplate>
//             </GroupStyle.HeaderTemplate>
//         </GroupStyle>
//     </DataGrid.GroupStyle>
// </DataGrid>
var groupedEmployees = AllEmployees.GroupBy(e => e.Department).ToList();

6. Count --- 计数

含义:统计集合元素总数,或满足条件的元素数量。

csharp 复制代码
// 总人数
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 --- 聚合统计

含义:对集合元素执行求和、求平均、最大 / 最小值的聚合运算。

csharp 复制代码
// 薪资合计
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 --- 取首元素

含义:获取序列第一个元素,无元素时返回默认值(避免异常)。

csharp 复制代码
// 安全获取第一个员工(无数据时返回 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 --- 存在性判断

含义:判断集合中是否存在至少一个满足条件的元素。

csharp 复制代码
// 控制按钮启用/禁用
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 --- 去重

含义:去除集合中的重复元素,返回唯一值序列。

csharp 复制代码
// 提取所有唯一部门,填充下拉列表
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<Employee>
{
    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 查询结果立即实例化为内存集合,避免延迟执行带来的重复查询。

csharp 复制代码
// ❌ 不推荐:延迟执行,绑定到 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<Employee>(
    AllEmployees.Where(e => e.Department == "IT").ToList());

// 需要数组时用 ToArray
string[] names = AllEmployees.Select(e => e.FullName).ToArray();

12. LINQ to XML --- 解析 XML 数据源

含义:针对 XDocument / XElement 执行查询,提取 XML 节点数据作为绑定源。

csharp 复制代码
// 假设 books.xml 内容包含 <Book> 节点
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)为内存集合,确保查询只执行一次。

五、总结

在 WPF 中,LINQ 的核心价值在于声明式数据处理,让开发者专注于"需要什么数据",而不是"如何遍历数据"。常用的组合模式为:

  1. Where 进行过滤。
  2. OrderBy 进行排序。
  3. Select 进行形状转换。
  4. ToList / ToArray 固化结果以便绑定。

这种链式调用不仅减少了代码量,还显著提升了代码的可读性和可维护性。

相关推荐
做个文艺程序员3 天前
MQ第12篇:AI原生消息架构演进:从MQ到事件驱动的全链路设计
架构·消息队列·wpf·事件驱动架构·ai-native·cqrs
donk13145 天前
React Native for OpenHarmony 实战:三方库 react-native-circular-action-menu 的鸿蒙化适配指南
javascript·分布式·react native·react.js·wpf·harmonyos
donk13146 天前
React Native for OpenHarmony 实战:三方库 react-native-step-indicator 的鸿蒙化适配指南
分布式·react native·react.js·wpf·harmonyos
He BianGu6 天前
【笔记】WPF中RenderOptions类的详细功能
笔记·wpf
VidDown6 天前
上线三个月后的第一次事故:一个 Django 服务的日志、监控与故障复盘
数据库·python·django·wpf·音视频·视频编解码
He BianGu6 天前
【笔记】WPF中CachingHint的功能
笔记·wpf
He BianGu6 天前
【笔记】在WPF中BitmapScalingMode的作用
笔记·wpf
Vae_Mars7 天前
幂等性学习笔记
笔记·学习·wpf