在代码Review时,一位同事的C#开发者写出了这样的代码:
C#if (orders.Count() > 0)
{
// 处理订单逻辑
}
看似没问题?实际上这行代码在处理大数据集时性能比较差!
作为C#开发者的日常利器,LINQ让我们的代码更优雅、更简洁。但正是因为它太好用,很多开发者(包括资深程序员)在不知不觉中踩坑,导致性能问题、内存泄漏,甚至运行时异常。
本文将揭露11个最常见但容易被忽视的LINQ误区,每个都配有完整的代码示例和解决方案,帮你写出更高效、更稳定的C#代码。
.ToList()错误示例:
C#// ❌ 错误写法:强制立即执行,造成双重迭代
var result = GetUsers().Where(u => u.IsActive).ToList();
if (result.Any())
{
// 处理逻辑
}
正确写法:
C#namespace AppLINQ11
{
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
}
public class Program
{
public static void Main()
{
// ✅ 正确写法:利用延迟执行
var activeUsers = GetUsers().Where(u => u.IsActive);
if (activeUsers.Any())
{
Console.WriteLine("找到活跃用户:");
var userList = activeUsers.ToList(); // 仅在真正需要List时才转换
foreach (var user in userList)
{
Console.WriteLine($"ID: {user.Id}, 姓名: {user.Name}");
}
}
else
{
Console.WriteLine("没有找到活跃用户");
}
}
public static IEnumerable<User> GetUsers()
{
Console.WriteLine("正在查询用户数据...");
return new List<User>
{
new User { Id = 1, Name = "张三", IsActive = true },
new User { Id = 2, Name = "李四", IsActive = false },
new User { Id = 3, Name = "王五", IsActive = true },
new User { Id = 4, Name = "赵六", IsActive = false }
};
}
}
}

💡 核心原理:
ToList() 会立即执行查询并创建新集合Any() 又会再次遍历Any() 只需检查第一个元素即可.Count() 检查是否为空错误示例:
C#// ❌ 错误写法:遍历整个集合来计数
if (orders.Count() > 0)
{
ProcessOrders(orders);
}
// ❌ 更糟糕的写法
if (orders.Where(o => o.IsValid).Count() > 0)
{
// 这会遍历所有元素!
}
正确写法:
C#public class Order
{
public int Id { get; set; }
public string Product { get; set; }
public bool IsValid { get; set; }
public decimal Amount { get; set; }
}
public class Program
{
public static void Main()
{
var orders = GetOrders();
// ✅ 正确写法:找到第一个元素就返回
if (orders.Any())
{
Console.WriteLine("有订单需要处理");
ProcessOrders(orders);
}
else
{
Console.WriteLine("没有订单");
}
Console.WriteLine("---");
// ✅ 带条件的正确写法
if (orders.Any(o => o.IsValid))
{
Console.WriteLine("找到有效订单,开始处理...");
// 找到第一个有效订单就返回true,性能更好
var validOrders = orders.Where(o => o.IsValid);
ProcessOrders(validOrders);
}
else
{
Console.WriteLine("没有有效订单");
}
}
public static void ProcessOrders(IEnumerable<Order> orders)
{
foreach (var order in orders)
{
Console.WriteLine($"处理订单: {order.Product} - {order.Amount:C}");
}
}
public static List<Order> GetOrders()
{
return new List<Order>
{
new Order { Id = 1, Product = "笔记本", IsValid = false, Amount = 5000m },
new Order { Id = 2, Product = "鼠标", IsValid = true, Amount = 100m },
new Order { Id = 3, Product = "键盘", IsValid = true, Amount = 300m }
};
}
}

⚡ 性能对比:
Count() > 0:O(n) 时间复杂度Any():O(1) 时间复杂度(最好情况)错误示例:
C#// ❌ 危险写法:运行时抛出 InvalidOperationException
foreach (var user in users.Where(u => u.IsBlocked))
{
users.Remove(user); // 💣 运行时炸弹
}
正确写法:
C#// ✅ 方案1:先收集,再删除
var toRemove = users.Where(u => u.IsBlocked).ToList();
foreach (var user in toRemove)
{
users.Remove(user);
}
// ✅ 方案2:使用RemoveAll(推荐)
users.RemoveAll(u => u.IsBlocked);
// ✅ 方案3:倒序遍历(适用于List),我记得20多年前做vb时,用这个搞listbox多
for (int i = users.Count - 1; i >= 0; i--)
{
if (users[i].IsBlocked)
users.RemoveAt(i);
}

错误示例:
C#// ❌ 错误写法:查询被执行了3次!
var expensiveQuery = GetData().Where(x => ComplexCalculation(x));
var count = expensiveQuery.Count(); // 第1次执行
var first = expensiveQuery.FirstOrDefault(); // 第2次执行
var list = expensiveQuery.ToList(); // 第3次执行
正确写法:
C#// ✅ 正确写法:只执行一次查询
var expensiveQuery = GetData().Where(x => ComplexCalculation(x)).ToList();
var count = expensiveQuery.Count; // 直接访问属性
var first = expensiveQuery.FirstOrDefault();
// list 就是 expensiveQuery 本身
📊 实际案例:某电商系统优化前后对比
.ToList() 转换错误示例:
C#// ❌ 错误写法:不必要的内存分配
public IEnumerable<UserDto> GetActiveUsers()
{
return users
.Where(u => u.IsActive)
.Select(u => new UserDto { Name = u.Name })
.ToList(); // 不必要的转换
}
正确写法:
C#namespace AppLINQ11
{
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
}
public class UserDto
{
public string Name { get; set; }
}
public class UserService
{
private readonly List<User> users;
public UserService()
{
users = new List<User>
{
new User { Id = 1, Name = "张三", IsActive = true },
new User { Id = 2, Name = "李四", IsActive = false },
new User { Id = 3, Name = "王五", IsActive = true },
new User { Id = 4, Name = "赵六", IsActive = false },
new User { Id = 5, Name = "钱七", IsActive = true }
};
}
// ✅ 正确写法:保持延迟执行
public IEnumerable<UserDto> GetActiveUsers()
{
Console.WriteLine("GetActiveUsers 被调用(但查询尚未执行)");
return users
.Where(u => u.IsActive)
.Select(u => new UserDto { Name = u.Name });
// 让调用者决定何时执行
}
}
public class Program
{
public static void Main()
{
var userService = new UserService();
Console.WriteLine("=== 延迟执行示例 ===");
// 调用者根据需要选择
var userStream = userService.GetActiveUsers(); // 延迟执行,此时还没真正查询
Console.WriteLine("获取了查询对象,但尚未执行查询");
Console.WriteLine("\n开始遍历(此时才真正执行查询):");
foreach (var user in userStream)
{
Console.WriteLine($"- {user.Name}");
}
Console.WriteLine("\n=== 立即执行示例 ===");
var userList = userService.GetActiveUsers().ToList(); // 立即执行
Console.WriteLine($"立即获取到 {userList.Count} 个活跃用户:");
foreach (var user in userList)
{
Console.WriteLine($"- {user.Name}");
}
Console.WriteLine("\n=== 多次使用延迟执行 ===");
var query = userService.GetActiveUsers();
// 第一次使用
Console.WriteLine("第一次统计:" + query.Count());
// 第二次使用(会重新执行查询)
Console.WriteLine("第二次遍历:");
foreach (var user in query.Take(2))
{
Console.WriteLine($"- {user.Name}");
}
}
}
}

FirstOrDefault 的空值风险错误示例:
C#// ❌ 危险写法:可能导致 NullReferenceException
var user = users.FirstOrDefault(u => u.Id == userId);
Console.WriteLine(user.Name); // 💣 可能抛出异常
正确写法:
C#// ✅ 方案1:传统null检查,我基本还在用这个写法,习惯了
var user = users.FirstOrDefault(u => u.Id == userId);
if (user != null)
{
Console.WriteLine(user.Name);
}
// ✅ 方案2:使用C# 8.0+ 的null条件运算符
var user = users.FirstOrDefault(u => u.Id == userId);
Console.WriteLine($"用户: {user?.Name ?? "不存在"} - {user?.Email ?? "无邮箱"}");
// ✅ 方案3:使用模式匹配(C# 8.0+)
if (users.FirstOrDefault(u => u.Id == userId) is { } foundUser)
{
Console.WriteLine($"模式匹配找到: {foundUser.Name} - {foundUser.Email}");
}

错误示例:
C#// ❌ 错误写法:每个元素都调用外部服务
var enrichedUsers = users
.Select(u => new
{
User = u,
Profile = externalService.GetProfile(u.Id) // 💸 昂贵的外部调用
})
.Where(x => x.Profile != null)
.ToList();
正确写法:
C#// ✅ 方案1:先过滤再丰富数据
var activeUsers = users.Where(u => u.IsActive).ToList();
var userIds = activeUsers.Select(u => u.Id).ToList();
// ✅ 方案2:使用并行处理(适用于独立操作)
var enrichedUsers = users
.AsParallel()
.Select(u => new
{
User = u,
Profile = externalService.GetProfile(u.Id)
})
.Where(x => x.Profile != null)
.ToList();
Skip 和 Take错误示例:
C#// ❌ 效率低下的分页
public List<Product> GetProducts(int page, int pageSize)
{
return allProducts // 这是内存中的完整数据集
.OrderBy(p => p.Name)
.Skip(page * pageSize) // 跳过前面所有记录
.Take(pageSize)
.ToList();
}
优化写法:
C#// ✅ 数据库层面的分页(推荐)
public async Task<List<Product>> GetProductsAsync(int page, int pageSize)
{
return await context.Products
.OrderBy(p => p.Name)
.Skip(page * pageSize)
.Take(pageSize)
.ToListAsync(); // 在数据库层面执行
}
错误示例:
C#// ❌ 错误写法:多次遍历集合
var result = users
.Where(u => u.IsActive) // 第1次遍历
.ToList() // 创建List
.Where(u => u.Age > 18) // 第2次遍历
.ToList() // 再次创建List
.Select(u => u.Name) // 第3次遍历
.ToList(); // 第3次创建List
正确写法:
C#// ✅ 正确写法:单次遍历
var result = users
.Where(u => u.IsActive && u.Age > 18) // 合并条件
.Select(u => u.Name) // 直接投影
.ToList(); // 只在最后物化
GroupBy错误示例:
C#// ❌ 错误写法:重复分组
var usersByDepartment = users.GroupBy(u => u.DepartmentId);
var departmentCounts = usersByDepartment.ToDictionary(g => g.Key, g => g.Count());
var departmentNames = usersByDepartment.ToDictionary(g => g.Key, g => g.First().DepartmentName);
正确写法:
C#// ✅ 正确写法:一次分组,多次使用
var usersByDepartment = users
.GroupBy(u => u.DepartmentId)
.ToList(); // 立即执行,避免重复分组
var departmentCounts = usersByDepartment.ToDictionary(g => g.Key, g => g.Count());
var departmentNames = usersByDepartment.ToDictionary(g => g.Key, g => g.First().DepartmentName);
// ✅ 更优雅的写法:使用匿名对象
var departmentStats = users
.GroupBy(u => u.DepartmentId)
.ToDictionary(g => g.Key, g => new
{
Count = g.Count(),
Name = g.First().DepartmentName,
Users = g.ToList()
});
错误示例:
C#// ❌ 危险写法:没有异常处理
var result = data
.Where(x => x != null)
.Select(x => int.Parse(x.Value)) // 可能抛出FormatException
.Average(); // 空集合会抛出InvalidOperationException
正确写法:
C#namespace AppLINQ11
{
public class DataItem
{
public string Value { get; set; }
}
// ✅ 使用LINQ安全扩展方法
public static class LinqExtensions
{
public static double? SafeAverage(this IEnumerable<int> source)
{
if (!source.Any()) return null;
return source.Average();
}
public static int? SafeMax(this IEnumerable<int> source)
{
if (!source.Any()) return null;
return source.Max();
}
}
public class Program
{
public static void Main()
{
// 模拟包含各种情况的数据
var data = GetSampleData();
Console.WriteLine("=== 原始数据 ===");
foreach (var item in data)
{
Console.WriteLine($"Value: '{item?.Value ?? "null"}'");
}
Console.WriteLine("\n=== 方案1:完善的异常处理 ===");
ProcessDataSafely(data);
Console.WriteLine("\n=== 方案2:使用扩展方法 ===");
ProcessDataWithExtensions(data);
}
// ✅ 正确写法:完善的异常处理
public static void ProcessDataSafely(List<DataItem> data)
{
var validNumbers = data
.Where(x => x?.Value != null) // 过滤null对象和null值
.Select(x =>
{
if (int.TryParse(x.Value, out int result))
return (int?)result; // 安全转换为可空int
return null;
})
.Where(x => x.HasValue) // 只保留成功转换的数字
.Select(x => x.Value); // 提取实际值
if (validNumbers.Any())
{
var average = validNumbers.Average();
var max = validNumbers.Max();
var count = validNumbers.Count();
Console.WriteLine($"有效数字数量: {count}");
Console.WriteLine($"平均值: {average:F2}");
Console.WriteLine($"最大值: {max}");
}
else
{
Console.WriteLine("没有有效的数字数据");
}
}
// ✅ 使用扩展方法的安全处理
public static void ProcessDataWithExtensions(List<DataItem> data)
{
var validNumbers = data
.Where(x => x?.Value != null)
.Select(x => int.TryParse(x.Value, out int result) ? (int?)result : null)
.Where(x => x.HasValue)
.Select(x => x.Value);
var safeAverage = validNumbers.SafeAverage();
var safeMax = validNumbers.SafeMax();
Console.WriteLine($"安全平均值: {safeAverage?.ToString("F2") ?? "无数据"}");
Console.WriteLine($"安全最大值: {safeMax?.ToString() ?? "无数据"}");
}
public static List<DataItem> GetSampleData()
{
return new List<DataItem>
{
new DataItem { Value = "10" },
new DataItem { Value = "abc" },
new DataItem { Value = "25" },
new DataItem { Value = null },
null,
new DataItem { Value = "" },
new DataItem { Value = "30" },
new DataItem { Value = "12.5" }
};
}
}
}

C#public static class SafeLinqExtensions
{
public static T SafeFirst<T>(this IEnumerable<T> source, T defaultValue = default)
where T : class
{
return source?.FirstOrDefault() ?? defaultValue;
}
public static double? SafeAverage<T>(this IEnumerable<T> source, Func<T, double> selector)
{
var items = source?.ToList();
return items?.Any() == true ? items.Average(selector) : null;
}
public static IEnumerable<TResult> ProcessInBatches<T, TResult>(
this IEnumerable<T> source,
int batchSize,
Func<IEnumerable<T>, TResult> processor)
{
var batch = new List<T>(batchSize);
foreach (var item in source)
{
batch.Add(item);
if (batch.Count == batchSize)
{
yield return processor(batch);
batch.Clear();
}
}
if (batch.Any())
yield return processor(batch);
}
}

ToList(),让LINQ的延迟执行为你优化性能Any() vs Count(),FirstOrDefault() vs First(),选择决定性能通过避免这11个错误,你的LINQ代码可以获得:
问题1:你在项目中遇到过哪些LINQ性能问题?是如何解决的?
问题2:除了文中提到的11个错误,你还发现过哪些常见的LINQ误区?
在评论区分享你的经验,让我们一起构建更好的C#开发实践!如果这篇文章对你有帮助,请转发给更多的C#开发同事,让我们一起写出更高效的代码!
#C#开发 #LINQ优化 #性能调优 #编程技巧
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!