在 C# 中优化 LINQ 查询的性能和可读性

985cd515756bbcfe716cabbec663372b.jpeg

概述:LINQ(语言集成查询)彻底改变了我们在 C# 中与数据交互的方式。它提供了一种一致、可读且简洁的方式来操作集合、数据库、XML 等。但是,LINQ 的美观性和易用性有时会掩盖性能缺陷。了解 LINQ 的基础 🤿在开始优化之前,必须了解 LINQ 在后台的工作原理。LINQ 查询可以在两种模式下运行:延迟执行和立即执行。了解这一点是优化查询的关键。延迟执行:查询不是在其声明点执行,而是在枚举点执行。这允许查询组合和有效的内存使用。立即执行:查询是即时执行的,通常由 、 、 等方法触发,当需要立即获得结果时很有用。方案 1:通过延迟执行🏃 ♂️💨减少内存占用问题陈述:您有大量客户数据,需要

LINQ(语言集成查询)彻底改变了我们在 C# 中与数据交互的方式。它提供了一种一致、可读且简洁的方式来操作集合、数据库、XML 等。但是,LINQ 的美观性和易用性有时会掩盖性能缺陷。

了解 LINQ 的基础 🤿

在开始优化之前,必须了解 LINQ 在后台的工作原理。LINQ 查询可以在两种模式下运行:延迟执行和立即执行。了解这一点是优化查询的关键。

延迟执行:查询不是在其声明点执行,而是在枚举点执行。这允许查询组合和有效的内存使用。

立即执行:查询是即时执行的,通常由 、 、 等方法触发,当需要立即获得结果时很有用。

方案 1:通过延迟执行🏃 ♂️💨减少内存占用

问题陈述:您有大量客户数据,需要根据某些条件筛选客户,但一次只需要处理一个客户。

次优方法:

List<Customer> customers = GetAllCustomers(); // Expensive operation  
List<Customer> filteredCustomers = customers.Where(c => c.IsActive).ToList();  
foreach (var customer in filteredCustomers)  
{  
    ProcessCustomer(customer); // Assume this is a lightweight operation  
}

优化方案:

IEnumerable<Customer> customers = GetAllCustomers(); // Deferred execution  
var filteredCustomers = customers.Where(c => c.IsActive); // Still deferred  
foreach (var customer in filteredCustomers)  
{  
    ProcessCustomer(customer);  
}

说明:在优化的解决方案中,我们避免为活动客户创建单独的列表,从而减少内存使用。筛选是枚举的一部分,可降低内存占用量并提高潜在的性能,尤其是对于大型数据集。

方案 2:使用选择投影🎯最小化执行时间

问题陈述:您需要来自大型数据集的详细信息,但您只需要每个项目的几个属性。

次优方法:

var products = GetAllProducts(); // Let's say this returns a list<Product>  
var productDetails = products.Select(p => new   
{   
    p.Id,   
    p.Name,   
    p.Price,  
    Description = p.Description.Substring(0, 100) // Assume each description is lengthy  
}).ToList();

优化方案:

var productDetails = GetAllProducts() // Deferred execution  
    .Select(p => new { p.Id, p.Name, p.Price, Description = p.Description.Substring(0, 100) })  
    .ToList();

说明:这里的关键是在调用前只投影需要的数据,减少内存占用并加快操作速度。这种方法最大限度地减少了正在处理和存储在内存中的数据量。ToList()

方案 3:避免多个枚举 🚫🔄

问题陈述:您正在对同一数据集执行多个操作(例如,过滤、计数、聚合)。

次优方法:

var customers = GetAllCustomers();  
if (customers.Any())  
{  
    var activeCustomers = customers.Where(c => c.IsActive);  
    Console.WriteLine($"Active Customers: {activeCustomers.Count()}");  
      
    var premiumCustomers = activeCustomers.Where(c => c.IsPremium);  
    Console.WriteLine($"Premium Customers: {premiumCustomers.Count()}");  
}

优化方案:

var customers = GetAllCustomers().ToList(); // Immediate execution  
if (customers.Any())  
{  
    var activeCustomersCount = customers.Count(c => c.IsActive);  
    Console.WriteLine($"Active Customers: {activeCustomersCount}");  
      
    var premiumCustomersCount = customers.Count(c => c.IsActive && c.IsPremium);  
    Console.WriteLine($"Premium Customers: {premiumCustomersCount}");  
}

说明:优化的解决方案通过利用即时执行来缓存结果并使用更有效的计数方法,减少了集合的迭代次数。

使用 GroupBy 和 Join 🤹 高效处理复杂查询

LINQ 的强大功能还扩展到复杂的操作,例如分组和联接数据集,如果处理不当,这些操作可能会变得效率低下。让我们深入研究这些操作常用的场景,并探索优化的方法。

方案 4:使用 GroupBy🧩 优化数据分组

问题陈述:您需要按客户 ID 对订单列表进行分组,以计算每个客户的总订单数。

次优方法:

var orders = GetAllOrders(); // Assume this returns a List\<Order>  
var groupedOrders = orders  
    .GroupBy(order => order.CustomerId)  
    .Select(group => new  
    {  
        CustomerId = group.Key,  
        TotalOrders = group.Count(),  
        TotalAmount = group.Sum(order => order.Amount)  
    })  
    .ToList();

优化方案:

var groupedOrders = GetAllOrders() // Deferred execution  
    .GroupBy(order => order.CustomerId)  
    .Select(group => new  
    {  
        CustomerId = group.Key,  
        TotalOrders = group.Count(),  
        TotalAmount = group.Sum(order => order.Amount)  
    })  
    .ToList();

说明:虽然差异可能看起来很微妙,但优化的解决方案强调了将延迟执行利用到最后一个负责时刻的重要性。此方法可确保分组逻辑尽可能接近数据源,从而显著降低处理大型数据集时的开销。

方案 5:使用联接简化数据检索

问题陈述:您需要将订单列表与客户列表联接,以显示订单详细信息以及客户信息。

次优方法:

var orders = GetAllOrders(); // Assume this returns a List\<Order>  
var customers = GetAllCustomers(); // Assume this returns a List<Customer>  
  
var orderDetails = (from order in orders  
                    join customer in customers on order.CustomerId equals customer.Id  
                    select new  
                    {  
                        order.Id,  
                        CustomerName = customer.Name,  
                        order.Amount  
                    }).ToList();

优化方案:

var orderDetails = GetAllOrders() // Deferred execution for orders  
    .Join(GetAllCustomers(), // Deferred execution for customers  
          order => order.CustomerId,  
          customer => customer.Id,  
          (order, customer) => new  
          {  
              order.Id,  
              CustomerName = customer.Name,  
              order.Amount  
          })  
    .ToList();

说明:优化的解决方案通过对联接中涉及的两个集合利用延迟执行,更有效地利用了 LINQ 的联接操作。此方法在数据源本机支持 LINQ 查询(例如实体框架)的情况下特别有用,因为它可以显著优化基础数据库查询。

利用 AsParallel 进行并行处理 🚀

问题陈述:您需要对大量项目执行计算密集型操作。

次优方法:

var data = GetData(); // Large collection of data  
var results = data.Select(item => Compute(item)).ToList();

优化方案:

var results = GetData() // Large collection of data  
    .AsParallel()  
    .Select(item => Compute(item))  
    .ToList();

说明:通过引入 ,LINQ 查询可以跨多个线程并行执行,从而可能显著提高 CPU 密集型操作的性能。但是,在使用此方法时,必须考虑线程安全性和并行化的开销。AsParallel()

通过批处理📦🔄高效处理大型数据集

问题陈述:您正在处理一个巨大的数据集,例如处理数据库中的记录,并且需要批量应用操作以避免内存溢出并提高性能。

次优方法:

var allRecords = GetAllRecords(); // Assume this returns millions of records  
foreach (var record in allRecords)  
{  
    ProcessRecord(record); // Inefficient with large datasets  
}

优化方案:

const int batchSize = 1000; // Optimal size depends on the scenario  
var allRecords = GetAllRecords(); // Deferred execution  
for (int i = 0; i < allRecords.Count(); i += batchSize)  
{  
    var batch = allRecords.Skip(i).Take(batchSize);  
    foreach (var record in batch)  
    {  
        ProcessRecord(record);  
    }  
}

说明:通过批量处理记录,可以显著减少内存占用,并可能通过优化系统功能的工作负载来提高性能。这种方法在处理无法一次全部加载到内存中的大型数据集时特别有效。

方案 5:使用高效的 LINQ 方法📊✨简化数据聚合

问题陈述:您需要跨大型数据集聚合数据,例如计算总和、平均值或其他复杂操作。

次优方法:

var products = GetAllProducts(); // Let's say this is a large dataset  
decimal totalRevenue = 0m;  
foreach (var product in products)  
{  
    totalRevenue += product.Price * product.UnitsSold;  
}

优化方案:

var totalRevenue = GetAllProducts()  
    .Sum(product => product.Price * product.UnitsSold);

说明:利用 LINQ 的内置聚合方法(如 、 、 等)可以显著简化代码,并通过利用内部优化来提高性能。此示例说明如何将复杂操作简化为一行可读代码。SumAverageMinMax

方案 6:组合谓词以实现高效筛选 🕵️ ♂️🔍

问题陈述:您需要对数据集应用多个筛选器,这可能会导致对数据进行多次传递。

次优方法:

var filteredResults = GetAllItems() // Assume this is an expensive operation  
    .Where(item => item.Category == "Electronics")  
    .Where(item => item.Price > 1000)  
    .Where(item => item.Rating > 4)  
    .ToList();

优化方案:

var filteredResults = GetAllItems()  
    .Where(item => item.Category == "Electronics" && item.Price > 1000 && item.Rating > 4)  
    .ToList();

说明:将多个谓词合并到一个子句中可以通过减少集合的迭代次数来提高可读性和性能。此方法可确保在一次传递中评估所有筛选条件。Where

使用查询语法📖🧹增强可读性和可维护性

场景:您的任务是联接多个数据集并执行复杂的操作,其中可读性变得至关重要。

次优方法:

var result = dataset1  
    .Join(dataset2, d1 => d1.Key, d2 => d2.Key, (d1, d2) => new { d1, d2 })  
    .Where(x => x.d1.SomeProperty == "SomeValue")  
    .Select(x => new { x.d1, x.d2.OtherProperty })  
    .ToList();

优化方案:

var result = (from d1 in dataset1  
              join d2 in dataset2 on d1.Key equals d2.Key  
              where d1.SomeProperty == "SomeValue"  
              select new { d1, OtherProperty = d2.OtherProperty }).ToList();

说明:虽然方法语法通常更简洁,但查询语法可以增强可读性,尤其是对于涉及联接、where 和 select 语句的复杂查询。它类似于 SQL,使熟悉数据库查询语言的人更容易访问它。

利用并行处理实现高性能 LINQ 查询 🌐⚡

问题陈述:您需要处理大量数据,其中每个元素的处理都独立于其他元素,并且您希望利用处理器的多个内核来加快操作速度。

次优方法:

var data = GetData(); // Assume this returns a large dataset  
foreach (var item in data)  
{  
    ProcessItem(item); // Time-consuming operation  
}

优化方案:

var data = GetData();  
Parallel.ForEach(data, item =>   
{  
    ProcessItem(item);  
});

或者,使用 PLINQ(并行 LINQ):

var data = GetData().AsParallel();  
data.ForAll(item =>   
{  
    ProcessItem(item);  
});

说明:通过使用 PLINQ 的方法,您可以利用多个处理器/内核来显著减少大型数据集的处理时间。这种方法非常适合 CPU 密集型操作,在这些操作中,任务可以并行执行,而不会相互依赖。但是,必须确保线程安全并了解并行化的开销。Parallel.ForEachAsParallel

方案 8:重构可重用性和组合 🧩♻️

问题陈述:在整个应用程序中,有多个 LINQ 查询,它们共享通用的筛选或转换逻辑。

次优方法:

// In various parts of the application  
var activeUsers = GetAllUsers().Where(user => user.IsActive && user.SignUpDate < DateTime.UtcNow.AddYears(-1));  
var premiumUsers = GetAllUsers().Where(user => user.IsPremium && user.SignUpDate < DateTime.UtcNow.AddYears(-1));

优化方案:

// Define reusable predicate  
Func<User, bool> isLongTermUser = user => user.SignUpDate< DateTime.UtcNow.AddYears(-1);  
  
// Apply in queries  
var activeUsers = GetAllUsers().Where(user => user.IsActive && isLongTermUser(user));  
var premiumUsers = GetAllUsers().Where(user => user.IsPremium && isLongTermUser(user));

说明: 通过将公共逻辑重构为可重用的谓词或选择器,可以增强 LINQ 查询的可维护性和可读性。这种方法促进了 DRY(不要重复自己)原则,使您的代码库更简洁、更易于更新。

方案 9:选择正确的数据结构进行查找 🔍🚀

问题陈述:您需要在大型数据集中按键进行频繁查找,从而影响性能。

次优方法:

List<Product> products = GetAllProducts(); // Assume this is a large list  
foreach (var order in orders)  
{  
    var product = products.FirstOrDefault(p => p.Id == order.ProductId);  
    ProcessOrder(order, product);  
}

优化方案:

var productLookup = GetAllProducts().ToDictionary(p => p.Id);  
foreach (var order in orders)  
{  
    if (productLookup.TryGetValue(order.ProductId, out var product))  
    {  
        ProcessOrder(order, product);  
    }  
}

说明:将列表转换为字典以进行查找操作可以大大提高性能,尤其是对于大型数据集。这种方法将查找的复杂性从 O(n) 降低到 O(1),使每个查找操作的时间恒定,而不管数据集的大小如何。

利用索引选择来增强性能 📈🔍

问题陈述:在循环访问集合以转换其元素时,有时需要当前元素的索引进行计算或其他操作。

次优方法:

var items = GetItems(); // Assume this returns a collection of items  
List<ResultItem> result = new List<ResultItem>();  
for (int i = 0; i < items.Count(); i++)  
{  
    result.Add(new ResultItem  
    {  
        Index = i,  
        TransformedValue = TransformValue(items[i], i) // A hypothetical method requiring the index  
    });  
}

优化方案:

var result = GetItems()  
    .Select((item, index) => new ResultItem  
    {  
        Index = index,  
        TransformedValue = TransformValue(item, index)  
    })  
    .ToList();

解释: LINQ 中的方法允许包含当前元素的索引的重载。此方法不仅通过删除显式循环来简化代码,而且还保留了与 LINQ 关联的声明性可读样式,同时可能利用延迟执行的好处。Select

利用提高数据库效率 💾➡️🚀IQueryable<T>

问题陈述:在使用 ORM(对象关系映射)工具(如 Entity Framework)时,尽量减少从数据库传输的数据以提高应用程序性能至关重要。

次优方法:

var users = dbContext.Users.ToList(); // Immediately executing the query and loading all users  
var filteredUsers = users.Where(user => user.IsActive).ToList();

优化方案:

var filteredUsers = dbContext.Users  
    .Where(user => user.IsActive)  
    .ToList(); // The filtering is applied at the database level

解释:通过使用返回的 ,过滤逻辑被转换为 SQL 并在数据库级别执行。这种方法大大减少了通过网络传输的数据量,因为只有筛选的记录才会加载到内存中。IQueryable<T>dbContext.Users

在 LINQ 🔄🚀 中使用异步流处理

问题陈述:在处理大型数据集或 IO 绑定操作时,异步处理可以提高响应能力和可伸缩性。

次优方法:

var tasks = GetTasks(); // Assume this returns a large collection of Task<T>  
List<Result> results = new List<Result>();  
foreach (var task in tasks)  
{  
    var result = await task;  
    results. Add(result);  
}

优化方案:

var tasks = GetTasks();  
var results = await Task.WhenAll(tasks);

或者,对于异步流 (IAsyncEnumerable<T>):

await foreach (var result in GetAsyncResults()) // Assume GetAsyncResults returns IAsyncEnumerable<T>  
{  
    ProcessResult(result); // Asynchronously process each result as it becomes available  
}

说明:优化的解决方案利用异步编程范式来提高 IO 绑定操作的效率。 对于并发等待多个任务特别有用,而异步流 () 允许在每个项目可用时对其进行处理,这样可以提高内存效率和响应速度。Task.WhenAllIAsyncEnumerable<T>

🔄 递归查询:遍历分层数据结构✨

问题陈述:
您有一个分层的数据结构,例如树或嵌套对象图,需要递归遍历和查询数据。您希望利用 LINQ 执行递归查询并从分层结构中提取信息。🤔

解决方案:
LINQ 提供了功能强大的运算符,允许您以递归方式遍历和查询分层数据结构。通过将 LINQ 运算符与递归技术相结合,可以轻松地浏览嵌套对象并提取相关信息。🔄

public class Employee  
{  
    public string Name { get; set; }  
    public List<Employee> Subordinates { get; set; }  
}  
  
public static IEnumerable<Employee> GetAllSubordinates(Employee employee)  
{  
    if (employee.Subordinates != null && employee.Subordinates.Any())  
    {  
        foreach (var subordinate in employee.Subordinates)  
        {  
            yield return subordinate;  
            foreach (var subSubordinate in GetAllSubordinates(subordinate))  
            {  
                yield return subSubordinate;  
            }  
        }  
    }  
}  
  
// Usage example  
var ceo = new Employee  
{  
    Name = "John",  
    Subordinates = new List<Employee>  
    {  
        new Employee { Name = "Alice", Subordinates = new List<Employee>  
        {  
            new Employee { Name = "Bob" },  
            new Employee { Name = "Charlie" }  
        } },  
        new Employee { Name = "David" }  
    }  
};  
  
var allEmployees = new[] { ceo }.Concat(GetAllSubordinates(ceo));  
  
foreach (var employee in allEmployees)  
{  
    Console.WriteLine(employee. Name);  
}

在此示例中,我们有一个“Employee”类,它表示组织中的员工。每个员工都可以有一个下属名单,形成一个层次结构。🌳

“GetAllSubordinates”方法是一个递归函数,它遍历员工层次结构并检索给定员工的所有下属。它使用“yield return”语句生成一个“IEnumerable<Employee>”,该语句以递归方式包含所有下属及其下属。📥

在用法示例中,我们创建了一个包含 CEO 及其下属的示例员工层次结构。然后,我们使用“Concat”运算符将 CEO 与其通过递归“GetAllSubordinates”方法获得的所有下属组合在一起。🔗

通过利用递归查询,可以使用 LINQ 轻松遍历和查询分层数据结构。在处理树状结构、嵌套对象图或以分层方式组织数据的任何方案时,此方法特别有用。🌿

🎨 查询组合:使用可重用部件🔦构建复杂查询

问题陈述:

随着 LINQ 查询变得越来越复杂并涉及多个操作,保持可读性和可重用性变得具有挑战性。您希望将复杂的查询分解为更小的、可重用的部分,并将它们组合在一起,以创建功能更强大、更具表现力的查询。🤔

解决方案:
查询组合是一种技术,它允许您通过组合较小的、可重用的查询部件来生成复杂的 LINQ 查询。通过将查询逻辑封装到单独的方法或变量中,可以创建更易于理解、维护和重用的模块化和可组合查询。🎨

public static class QueryExtensions  
{  
    public static IEnumerable<Employee> GetSeniorEmployees(this IEnumerable<Employee> employees)  
    {  
        return employees.Where(e => e.YearsOfExperience >= 5);  
    }  
      
    public static IEnumerable<Employee> GetEmployeesByDepartment(this IEnumerable<Employee> employees, string department)  
    {  
        return employees.Where(e => e.Department == department);  
    }  
      
    public static IEnumerable<string> GetFullNames(this IEnumerable<Employee> employees)  
    {  
        return employees.Select(e => $"{e.FirstName} {e.LastName}");  
    }  
}  
  
// Usage example  
List<Employee> employees = GetEmployees();  
  
var seniorSalesEmployees = employees  
    .GetSeniorEmployees()  
    .GetEmployeesByDepartment("Sales");  
  
var seniorSalesEmployeeNames = seniorSalesEmployees.GetFullNames();  
  
foreach (var name in seniorSalesEmployeeNames)  
{  
    Console.WriteLine(name);  
}

在此示例中,我们在“QueryExtensions”类中定义了一组扩展方法。每个方法都表示一个可重用的查询部件,该部件执行特定操作,例如筛选高级员工、按部门筛选员工或投影员工姓名。📂

通过将这些查询部分组合在一起,我们可以创建更复杂、更具表现力的查询。在用法示例中,我们首先使用“GetSeniorEmployees”方法检索高级员工,然后使用“GetEmployeesByDepartment”方法按销售部门筛选他们。最后,我们使用“GetFullNames”方法投影生成的员工的全名。🔧

查询组合可提高代码的可重用性、模块化和可维护性。通过将复杂的查询分解为更小的集中部分,您可以轻松地修改、测试和重用单个查询组件。此方法还通过提供清晰且结构化的方式来构建和理解复杂查询,从而增强了可读性。📖

结论:超越优化——干净有效的代码🎨📚的艺术

优化 LINQ 查询不仅仅是榨取性能的每一点。它是关于编写高效、易于理解和可维护的代码。通过将这些高级 LINQ 技术和原则集成到您的开发实践中,您可以创建不仅性能良好,而且易于处理和随时间推移而发展的应用程序。请记住,学习和改进的旅程永无止境。保持好奇心,尝试新想法,并不断完善编写干净、有效代码的方法。

如果你喜欢我的文章,请给我一个赞!谢谢

-

技术群:添加小编微信并备注进群

小编微信:mm1552923   

公众号:dotNet编程大全    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值