Skip to content

C# LINQ (语言集成查询)

概述

LINQ(语言集成查询)为处理来自不同来源的数据提供了统一的查询语法。它允许您直接在 C# 代码中编写查询。

基本 LINQ 概念

LINQ 查询语法

csharp
using System.Linq;

// 数据源
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// 查询语法
var evenNumbers = from num in numbers
                 where num % 2 == 0
                 select num;

// 方法语法(等价)
var evenNumbersMethod = numbers.Where(num => num % 2 == 0);

// 执行查询
foreach (var num in evenNumbers)
{
    Console.WriteLine(num); // 2, 4, 6, 8, 10
}

LINQ 方法语法

csharp
List<string> words = new List<string>
{
    "apple", "banana", "cherry", "date", "elderberry"
};

// 筛选
var longWords = words.Where(w => w.Length > 5);

// 投影
var wordLengths = words.Select(w => new { Word = w, Length = w.Length });

// 排序
var sortedWords = words.OrderBy(w => w);

// 分组
var wordsByLength = words.GroupBy(w => w.Length);

LINQ 操作符

筛选操作符

csharp
public class LinqFiltering
{
    public static void DemonstrateFiltering()
    {
        var products = new[]
        {
            new { Id = 1, Name = "Laptop", Price = 999.99m, Category = "Electronics" },
            new { Id = 2, Name = "Book", Price = 19.99m, Category = "Books" },
            new { Id = 3, Name = "Phone", Price = 699.99m, Category = "Electronics" },
            new { Id = 4, Name = "Pen", Price = 2.99m, Category = "Office" }
        };

        // Where 子句
        var electronics = products.Where(p => p.Category == "Electronics");
        var expensive = products.Where(p => p.Price > 500);
        var cheapBooks = products.Where(p => p.Category == "Books" && p.Price < 20);

        Console.WriteLine("Electronics:");
        foreach (var item in electronics)
        {
            Console.WriteLine($"{item.Name}: ${item.Price}");
        }
    }
}

投影操作符

csharp
public class LinqProjection
{
    public static void DemonstrateProjection()
    {
        var students = new[]
        {
            new { FirstName = "John", LastName = "Doe", Age = 20, Grade = 85 },
            new { FirstName = "Jane", LastName = "Smith", Age = 22, Grade = 92 },
            new { FirstName = "Bob", LastName = "Johnson", Age = 21, Grade = 78 }
        };

        // Select - 转换为新类型
        var studentNames = students.Select(s => $"{s.FirstName} {s.LastName}");
        var studentInfo = students.Select(s => new
        {
            FullName = $"{s.FirstName} {s.LastName}",
            IsAdult = s.Age >= 18
        });

        // SelectMany - 展平集合
        var classes = new[]
        {
            new { Name = "Math", Students = new[] { "Alice", "Bob" } },
            new { Name = "Science", Students = new[] { "Charlie", "David", "Eve" } }
        };

        var allStudents = classes.SelectMany(c => c.Students);

        Console.WriteLine("All students:");
        foreach (var student in allStudents)
        {
            Console.WriteLine(student);
        }
    }
}

排序操作符

csharp
public class LinqOrdering
{
    public static void DemonstrateOrdering()
    {
        var people = new[]
        {
            new { Name = "Alice", Age = 30, Salary = 50000 },
            new { Name = "Bob", Age = 25, Salary = 45000 },
            new { Name = "Charlie", Age = 35, Salary = 60000 },
            new { Name = "David", Age = 28, Salary = 52000 }
        };

        // OrderBy
        var byAge = people.OrderBy(p => p.Age);
        var byName = people.OrderBy(p => p.Name);

        // OrderByDescending
        var bySalaryDesc = people.OrderByDescending(p => p.Salary);

        // ThenBy 用于次要排序
        var byAgeThenName = people
            .OrderBy(p => p.Age)
            .ThenBy(p => p.Name);

        Console.WriteLine("People ordered by age:");
        foreach (var person in byAge)
        {
            Console.WriteLine($"{person.Name}, Age: {person.Age}");
        }
    }
}

分组操作符

csharp
public class LinqGrouping
{
    public static void DemonstrateGrouping()
    {
        var products = new[]
        {
            new { Name = "Laptop", Category = "Electronics", Price = 999.99m },
            new { Name = "Mouse", Category = "Electronics", Price = 29.99m },
            new { Name = "Book", Category = "Books", Price = 19.99m },
            new { Name = "Pen", Category = "Office", Price = 2.99m },
            new { Name = "Phone", Category = "Electronics", Price = 699.99m }
        };

        // GroupBy
        var groupedByCategory = products.GroupBy(p => p.Category);

        foreach (var group in groupedByCategory)
        {
            Console.WriteLine($"Category: {group.Key}");
            foreach (var product in group)
            {
                Console.WriteLine($"  {product.Name}: ${product.Price}");
            }
        }

        // GroupBy 带投影
        var categoryStats = products
            .GroupBy(p => p.Category)
            .Select(g => new
            {
                Category = g.Key,
                Count = g.Count(),
                TotalPrice = g.Sum(p => p.Price),
                AveragePrice = g.Average(p => p.Price)
            });

        Console.WriteLine("\nCategory statistics:");
        foreach (var stat in categoryStats)
        {
            Console.WriteLine($"{stat.Category}: {stat.Count} items, " +
                          $"Avg: ${stat.AveragePrice:F2}");
        }
    }
}

集合操作

csharp
public class LinqSetOperations
{
    public static void DemonstrateSetOperations()
    {
        var set1 = new[] { 1, 2, 3, 4, 5 };
        var set2 = new[] { 4, 5, 6, 7, 8 };

        // Union 并集
        var union = set1.Union(set2);
        Console.WriteLine($"Union: {string.Join(", ", union)}");

        // Intersection 交集
        var intersection = set1.Intersect(set2);
        Console.WriteLine($"Intersection: {string.Join(", ", intersection)}");

        // Except 差集
        var except = set1.Except(set2);
        Console.WriteLine($"Except: {string.Join(", ", except)}");

        // Distinct 去重
        var numbers = new[] { 1, 2, 2, 3, 3, 3, 4 };
        var distinct = numbers.Distinct();
        Console.WriteLine($"Distinct: {string.Join(", ", distinct)}");
    }
}

LINQ 与集合

LINQ 与列表

csharp
public class LinqWithLists
{
    public static void DemonstrateListOperations()
    {
        var employees = new List<Employee>
        {
            new Employee { Id = 1, Name = "Alice", Department = "IT", Salary = 60000 },
            new Employee { Id = 2, Name = "Bob", Department = "HR", Salary = 50000 },
            new Employee { Id = 3, Name = "Charlie", Department = "IT", Salary = 65000 },
            new Employee { Id = 4, Name = "David", Department = "Finance", Salary = 55000 }
        };

        // 查找特定员工
        var employee = employees.FirstOrDefault(e => e.Id == 2);

        // 按部门筛选
        var itEmployees = employees.Where(e => e.Department == "IT").ToList();

        // 计算统计数据
        var totalSalary = employees.Sum(e => e.Salary);
        var averageSalary = employees.Average(e => e.Salary);
        var maxSalary = employees.Max(e => e.Salary);

        // 检查条件
        var hasHighEarner = employees.Any(e => e.Salary > 70000);
        var allHaveSalary = employees.All(e => e.Salary > 0);

        Console.WriteLine($"Total salary: ${totalSalary:N2}");
        Console.WriteLine($"Average salary: ${averageSalary:N2}");
        Console.WriteLine($"Max salary: ${maxSalary:N2}");
    }
}

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Department { get; set; }
    public decimal Salary { get; set; }
}

LINQ 与字典

csharp
public class LinqWithDictionaries
{
    public static void DemonstrateDictionaryOperations()
    {
        var productPrices = new Dictionary<string, decimal>
        {
            ["Laptop"] = 999.99m,
            ["Mouse"] = 29.99m,
            ["Keyboard"] = 79.99m,
            ["Monitor"] = 299.99m
        };

        // 筛选字典
        var expensive = productPrices.Where(kvp => kvp.Value > 100);

        // 转换为不同类型
        var productList = productPrices
            .Select(kvp => new { Name = kvp.Key, Price = kvp.Value })
            .OrderBy(p => p.Price);

        // 按价格范围分组
        var priceGroups = productPrices
            .GroupBy(kvp => kvp.Value < 50 ? "Cheap" :
                           kvp.Value < 200 ? "Medium" : "Expensive");

        Console.WriteLine("Products by price range:");
        foreach (var group in priceGroups)
        {
            Console.WriteLine($"{group.Key}:");
            foreach (var item in group)
            {
                Console.WriteLine($"  {item.Key}: ${item.Value}");
            }
        }
    }
}

高级 LINQ

使用自定义比较器的 LINQ

csharp
public class CustomComparer : IEqualityComparer<string>
{
    public bool Equals(string x, string y)
    {
        return string.Equals(x, y, StringComparison.OrdinalIgnoreCase);
    }

    public int GetHashCode(string obj)
    {
        return obj?.ToUpperInvariant().GetHashCode() ?? 0;
    }
}

public class LinqCustomComparers
{
    public static void DemonstrateCustomComparers()
    {
        var words = new[] { "Apple", "apple", "Banana", "banana", "Cherry" };

        // 不区分大小写的去重
        var distinctWords = words.Distinct(new CustomComparer());

        Console.WriteLine("Distinct words (case-insensitive):");
        foreach (var word in distinctWords)
        {
            Console.WriteLine(word);
        }
    }
}

使用匿名类型的 LINQ

csharp
public class LinqAnonymousTypes
{
    public static void DemonstrateAnonymousTypes()
    {
        var people = new[]
        {
            new { FirstName = "John", LastName = "Doe", Age = 30, City = "New York" },
            new { FirstName = "Jane", LastName = "Smith", Age = 25, City = "Los Angeles" },
            new { FirstName = "Bob", LastName = "Johnson", Age = 35, City = "Chicago" }
        };

        // 创建匿名类型
        var summary = people
            .GroupBy(p => p.City)
            .Select(g => new
            {
                City = g.Key,
                Count = g.Count(),
                AverageAge = g.Average(p => p.Age),
                Names = g.Select(p => $"{p.FirstName} {p.LastName}").ToList()
            });

        foreach (var city in summary)
        {
            Console.WriteLine($"City: {city.City}");
            Console.WriteLine($"  Count: {city.Count}");
            Console.WriteLine($"  Average Age: {city.AverageAge:F1}");
            Console.WriteLine($"  Names: {string.Join(", ", city.Names)}");
        }
    }
}

LINQ to XML

使用 LINQ 查询 XML

csharp
using System.Xml.Linq;

public class LinqToXml
{
    public static void DemonstrateXmlQuerying()
    {
        // 创建 XML 文档
        var xml = XElement.Parse(@"
            <books>
                <book id='1' category='Fiction'>
                    <title>The Great Gatsby</title>
                    <author>F. Scott Fitzgerald</author>
                    <price>12.99</price>
                </book>
                <book id='2' category='Non-Fiction'>
                    <title>Thinking, Fast and Slow</title>
                    <author>Daniel Kahneman</author>
                    <price>16.99</price>
                </book>
                <book id='3' category='Fiction'>
                    <title>1984</title>
                    <author>George Orwell</author>
                    <price>14.99</price>
                </book>
            </books>");

        // 使用 LINQ 查询 XML
        var fictionBooks = from book in xml.Elements("book")
                         where (string)book.Attribute("category") == "Fiction"
                         select new
                         {
                             Title = (string)book.Element("title"),
                             Author = (string)book.Element("author"),
                             Price = (decimal)book.Element("price"),
                             Id = (int)book.Attribute("id")
                         };

        Console.WriteLine("Fiction books:");
        foreach (var book in fictionBooks)
        {
            Console.WriteLine($"{book.Title} by {book.Author} - ${book.Price}");
        }
    }
}

性能考虑

延迟执行与立即执行

csharp
public class LinqPerformance
{
    public static void DemonstrateExecution()
    {
        var numbers = Enumerable.Range(1, 1000000);

        // 延迟执行 - 查询尚未执行
        var query = numbers.Where(n => n % 2 == 0)
                        .Select(n => n * 2);

        // 立即执行 - 现在执行查询
        var result = query.ToList();

        // 使用 ToList()、ToArray()、Count() 等强制立即执行
        var count = numbers.Count(n => n > 500000);

        Console.WriteLine($"Count: {count}");
        Console.WriteLine($"First 5 results: {string.Join(", ", result.Take(5))}");
    }
}

优化 LINQ 查询

csharp
public class LinqOptimization
{
    public static void DemonstrateOptimization()
    {
        var largeList = Enumerable.Range(1, 1000000).ToList();

        // 不好:多次枚举
        // var count = largeList.Where(n => n > 500000).Count();
        // var sum = largeList.Where(n => n > 500000).Sum();

        // 好:单次枚举
        var filtered = largeList.Where(n => n > 500000);
        var count = filtered.Count();
        var sum = filtered.Sum();

        // 更好:使用特定的操作符
        var count2 = largeList.Count(n => n > 500000);
        var sum2 = largeList.Where(n => n > 500000).Sum();

        Console.WriteLine($"Count: {count}, Sum: {sum}");
    }
}

实际示例

数据分析示例

csharp
public class DataAnalysis
{
    public static void AnalyzeSalesData()
    {
        var sales = new[]
        {
            new { Date = DateTime.Parse("2023-01-01"), Product = "Laptop", Amount = 1200, Region = "North" },
            new { Date = DateTime.Parse("2023-01-02"), Product = "Mouse", Amount = 30, Region = "South" },
            new { Date = DateTime.Parse("2023-01-03"), Product = "Laptop", Amount = 1100, Region = "East" },
            new { Date = DateTime.Parse("2023-01-04"), Product = "Keyboard", Amount = 80, Region = "North" },
            new { Date = DateTime.Parse("2023-01-05"), Product = "Mouse", Amount = 35, Region = "West" }
        };

        // 按地区销售
        var salesByRegion = sales
            .GroupBy(s => s.Region)
            .Select(g => new
            {
                Region = g.Key,
                TotalSales = g.Sum(s => s.Amount),
                AverageSale = g.Average(s => s.Amount),
                Count = g.Count()
            })
            .OrderByDescending(r => r.TotalSales);

        Console.WriteLine("Sales by Region:");
        foreach (var region in salesByRegion)
        {
            Console.WriteLine($"{region.Region}: Total=${region.TotalSales:N2}, " +
                          $"Avg=${region.AverageSale:N2}, Count={region.Count}");
        }

        // 按产品销售
        var salesByProduct = sales
            .GroupBy(s => s.Product)
            .Select(g => new
            {
                Product = g.Key,
                TotalRevenue = g.Sum(s => s.Amount),
                UnitsSold = g.Count(),
                AveragePrice = g.Average(s => s.Amount)
            });

        Console.WriteLine("\nSales by Product:");
        foreach (var product in salesByProduct)
        {
            Console.WriteLine($"{product.Product}: Revenue=${product.TotalRevenue:N2}, " +
                          $"Units={product.UnitsSold}, Avg Price=${product.AveragePrice:N2}");
        }
    }
}

报告生成

csharp
public class ReportGenerator
{
    public static void GenerateEmployeeReport()
    {
        var employees = new[]
        {
            new { Name = "Alice", Department = "IT", Salary = 60000, HireDate = DateTime.Parse("2020-01-15") },
            new { Name = "Bob", Department = "HR", Salary = 50000, HireDate = DateTime.Parse("2019-03-20") },
            new { Name = "Charlie", Department = "IT", Salary = 65000, HireDate = DateTime.Parse("2021-06-10") },
            new { Name = "David", Department = "Finance", Salary = 55000, HireDate = DateTime.Parse("2018-11-05") }
        };

        // 部门摘要
        var departmentSummary = employees
            .GroupBy(e => e.Department)
            .Select(g => new
            {
                Department = g.Key,
                EmployeeCount = g.Count(),
                TotalSalary = g.Sum(e => e.Salary),
                AverageSalary = g.Average(e => e.Salary),
                NewestHire = g.Max(e => e.HireDate),
                Employees = g.OrderBy(e => e.Name).ToList()
            });

        Console.WriteLine("=== Employee Report ===");
        foreach (var dept in departmentSummary)
        {
            Console.WriteLine($"\nDepartment: {dept.Department}");
            Console.WriteLine($"Employees: {dept.EmployeeCount}");
            Console.WriteLine($"Total Salary: ${dept.TotalSalary:N2}");
            Console.WriteLine($"Average Salary: ${dept.AverageSalary:N2}");
            Console.WriteLine($"Newest Hire: {dept.NewestHire:yyyy-MM-dd}");
            Console.WriteLine("Employees:");

            foreach (var emp in dept.Employees)
            {
                int yearsOfService = (int)((DateTime.Now - emp.HireDate).TotalDays / 365);
                Console.WriteLine($"  {emp.Name} - {yearsOfService} years");
            }
        }
    }
}

最佳实践

LINQ 指南

csharp
// 好:使用适当的 LINQ 方法
var result = data.Where(x => x.IsValid)
               .OrderBy(x => x.Name)
               .Take(10);

// 不好:复杂的嵌套查询
var badResult = data.Where(x =>
    data.Where(y => y.Id == x.ParentId)
         .Select(y => y.Name)
         .FirstOrDefault() == "Expected");

// 好:将复杂查询分解为步骤
var parentIds = data.Where(x => x.IsValid).Select(x => x.ParentId);
var parentNames = data.Where(x => parentIds.Contains(x.Id))
                   .ToDictionary(x => x.Id, x => x.Name);

性能提示

csharp
// 好:尽可能使用特定的操作符
var count = collection.Count(x => x.IsActive);

// 不好:当存在带谓词的 Count() 时使用 Where().Count()
var badCount = collection.Where(x => x.IsActive).Count();

// 好:对数据库查询使用 AsEnumerable()
var dbResults = dbContext.Products
    .Where(p => p.Price > 100)
    .AsEnumerable()
    .Select(p => new { p.Name, FormattedPrice = $"${p.Price:N2}" });

总结

在本章中,您学习了:

  • LINQ 查询语法与方法语法
  • 筛选、投影、排序和分组操作符
  • 集合操作和聚合
  • 不同集合类型的 LINQ
  • 自定义比较器等高级功能
  • LINQ to XML
  • 性能考虑和最佳实践

LINQ 提供了一种强大、一致的方式来查询和操作来自各种来源的数据。掌握 LINQ 将使您在处理 C# 中的集合和数据时更加高效。