应避免在循环中频繁创建对象,需将可复用对象(如StringBuilder、List)提至循环外;优先用struct替代class(注意装箱);LINQ慎用延迟执行与嵌套;大对象复用ArrayPool;IDisposable必须using释放;减少装箱和字符串隐式转换。

避免在循环中反复创建对象
频繁在 for 或 foreach 中 new 对象(尤其是字符串、集合、临时类实例)会快速推高 GC 压力,尤其在高频调用路径上。.NET 的 GC 虽然高效,但 Gen 0 频繁触发仍会拖慢吞吐。
- 把可复用的对象提到循环外,例如
StringBuilder、List、自定义缓存池中的实例 - 字符串拼接优先用
StringBuilder.Append(),而非+=;后者每次都会生成新字符串对象 - 若循环内必须构造轻量对象,考虑用
struct替代class(注意装箱风险和值语义是否符合逻辑)
谨慎使用 LINQ 和延迟执行
Where、Select、OrderBy 等 LINQ 方法返回的是 IEnumerable,实际执行发生在遍历时。多次遍历同一查询会重复执行整个链路,可能反复分配中间集合或迭代器状态机。
- 确认只用一次时,保持延迟执行没问题;若需多次访问结果,显式调用
.ToList()或.ToArray()缓存,但要注意这会立即分配内存 - 避免在性能敏感路径写嵌套 LINQ,如
list.Where(...).Select(...).First()→ 改用传统for+ 提前break更可控 -
AsEnumerable()不会节省内存,它只是把 IQueryable 转为 IEnumerable,反而可能丢失数据库侧优化
及时释放大对象和非托管资源
.NET 中大于 85 KB 的对象会被分配到大对象堆(LOH),GC 不会压缩 LOH,碎片化后容易触发 Full GC。同时,IDisposable 类型(如 FileStream、Bitmap、DbContext)若未释放,会导致非托管内存泄漏,GC 无法自动回收。
- 大数组、大字节数组、大字符串尽量复用,可用
ArrayPool替代.Shared.Rent() new byte[1024*1024] - 所有实现
IDisposable的对象必须用using语句或显式调用Dispose(),尤其在异步方法中注意await using - 不要依赖析构函数(
~MyClass())做关键清理——它不保证何时运行,也不能替代IDisposable
减少装箱与字符串隐式转换
值类型转 object 或接口类型(如 int → IComparable)会触发装箱,分配堆内存;字符串格式化中滥用 $"{value}" 或 ToString() 在循环里也会累积小对象。
- 避免将
int、bool等传给接收object的老接口;改用泛型重载(如ConcurrentDictionary而非Hashtable) - 日志或调试输出中拼接字符串,优先用
string.Create()或Span构造,而非$"id={id}, name={name}" - 数值转字符串且格式固定时,用
int.TryFormat()写入预分配的Span,比ToString("D6")少一次分配
实际压测中,一个高频服务接口从每秒分配 20 MB 降到 1.2 MB,主要改动就是把循环内 new Dictionary 换成复用的 Dictionary.Clear(),再加一处 ArrayPool 租赁。这些点不难改,但容易被忽略——特别是当代码能跑通、没明显卡顿时。










