LINQ的使用,往往在"巧妙"与"邪恶"之间只有一线之隔。
我曾深刻领悟这一点——在一次代码评审中,我自信满满地展示了一段自认为堪称艺术品的LINQ代码,结果看着资深开发者的表情逐渐扭曲。"这……确实能跑,"他缓缓说道,"但今晚我可能会因为这段代码失眠。"
LINQ不仅是一种工具,更是一种武器。 而任何武器,既能行善,也能作恶。
以下是我用过的5个最具争议的LINQ技巧(后来恨不得从未写过):
var processedOrders = orders.Select(order => {
order.Total = ApplyRegionalTax(order.Total);
AuditLog.Write($"Processed: {order.Id}");
return order;
}).ToList();
为何邪恶?
LINQ本应只用于查询,而非执行副作用。Select
应该是纯函数——仅处理数据,而非记录日志或修改状态。
正确做法:先处理集合,再投影
foreach (var order in orders)
{
order.Total = ApplyRegionalTax(order.Total);
AuditLog.Write($"Processed: {order.Id}");
}
var processedOrders = orders.ToList();
var results = dbContext.Products
.Where(p => p.IsActive)
.ToList() // ← 邪恶的天才时刻
.Select(p => ExpensiveTransformation(p));
为何邪恶?
ToList()
会先将所有活跃产品加载到内存,再执行转换。如果数据量大,性能直接爆炸。
正确做法:让数据库先过滤
var results = dbContext.Products
.Where(p => p.IsActive)
.AsEnumerable() // 在数据库过滤后切换至LINQ-to-Objects
.Select(p => ExpensiveTransformation(p));
var allPermissions = users
.SelectMany(u => u.Roles)
.SelectMany(r => r.Permissions)
.Distinct();
为何邪恶?
如果 Users
或 Roles
数据量大,这会引发笛卡尔积爆炸。更糟的是,由于延迟执行,爆炸可能发生在远离代码的地方。
正确做法:尽早物化或重构查询
var roleIds = users.SelectMany(u => u.Roles).Select(r => r.Id).Distinct();
var permissions = dbContext.Permissions.Where(p => roleIds.Contains(p.RoleId));
var csvLine = orders.Aggregate("", (acc, order) => $"{acc},{order.Id}");
为何邪恶?
Aggregate
是可读性黑洞,且会生成N次字符串分配(GC压力警告⚠️)。
正确做法:像文明人一样用 string.Join
var csvLine = string.Join(",", orders.Select(o => o.Id));
var customerNames = orders
.Select(o => o.Customer?.Name ?? "Ghost Customer")
.ToList();
为何邪恶?
LINQ中的空检查会掩盖数据问题。如果 Customer
为 null
,这是否是Bug?查询默默掩盖了它。
正确做法:快速失败或先过滤
var validOrders = orders.Where(o => o.Customer != null);
var customerNames = validOrders.Select(o => o.Customer.Name);
"能力越大,责任越大。"
LINQ赋予我们强大的表达能力,但滥用会导致代码变得晦涩、低效,甚至埋下隐藏Bug。
你曾经写过哪些"创意十足"的LINQ技巧? 😈