IMemoryCache需通过DI注册并合理配置过期策略、大小限制和缓存键命名空间;避免手动new、混用过期方式、忽略SetSize及外部修改可变对象,否则将导致缓存失效、内存溢出或数据污染。

直接用 IMemoryCache 就行,但多数人卡在“缓存没生效”“值变了但缓存还是旧的”“内存爆了却不知道为什么”——问题不在会不会用,而在怎么设过期、怎么标大小、怎么避免共享对象污染。
MemoryCache 必须通过 DI 注册,别 new
手动 new MemoryCache(new MemoryCacheOptions()) 看似简单,实际会绕过 ASP.NET Core 的生命周期管理,导致缓存实例不统一、回调不触发、内存限制失效。尤其在 Web 项目里,多个 Controller 或 Service 拿到的可能是不同实例,缓存完全不共享。
- ASP.NET Core(.NET 6+):在
Program.cs中调用builder.Services.AddMemoryCache() - 控制台或 .NET Framework 项目:先安装 NuGet 包
Microsoft.Extensions.Caching.Memory,再注册;不推荐直接 new - 单元测试中若需手动实例化,至少配
SizeLimit和CompactionPercentage,否则默认无内存上限,容易 OOM
Set 和 GetOrCreate 的过期策略不能混用 AbsoluteExpiration 和 SlidingExpiration
MemoryCacheEntryOptions 允许同时设置 AbsoluteExpirationRelativeToNow 和 SlidingExpiration,但两者逻辑冲突:前者强制到期即删,后者每次访问重置计时。运行时不会报错,但行为不可预测——比如滑动时间设了 10 分钟,绝对时间设了 5 分钟,结果就是 5 分钟后必删,滑动失效。
- 高频读+低更新场景(如配置项):用
AbsoluteExpirationRelativeToNow - 活跃数据(如用户会话态):用
SlidingExpiration - 二者选其一,不要同时设;若真要“最长 30 分钟 + 活跃就续命”,得自己封装逻辑,靠
PostEvictionCallback触发重加载
大对象必须显式 SetSize,否则 size 计算严重失真
启用内存大小限制(SizeLimit)后,MemoryCache 靠每项的 SetSize() 值累加判断是否该清理。但默认所有未调用 SetSize() 的项都算作大小为 1 —— 一个 byte[1024*1024] 和一个字符串都算 “1”,内存紧张时优先淘汰大对象?根本不会发生。
- 注册时开启限制:
services.AddMemoryCache(options => options.SizeLimit = 1024 * 1024 * 100)(单位是估算字节数,非精确) - 存大对象时必须标大小:
.SetSize(1024)表示该项占约 1KB;小对象可不标,但建议统一标为 1 - 别用
sizeof或Marshal.SizeOf算对象大小——它们只算托管头和字段指针,不算实际堆内存;按经验估或序列化后取长度
GetOrCreate 不是线程安全的银弹,外部修改会污染缓存
GetOrCreate 看起来能防并发重复加载,但它只保证“工厂函数只执行一次”,不保证返回的对象本身是线程安全的。如果缓存的是 List<t></t> 或可变 DTO,外部代码直接改它,所有后续 Get 都拿到脏数据。
- 缓存前做深拷贝:
entry.SetValue(JsonSerializer.Deserialize<user>(JsonSerializer.Serialize(user)))</user> - 或改用不可变类型:
record User(string Name, int Id) - 更稳妥的做法:缓存只存 ID 或轻量快照,真实数据走 DB 查,用缓存控频次而非内容
最常被忽略的一点:缓存键没加命名空间前缀,比如直接用 "123" 而不是 "user:123",不同业务模块写同一个 key,互相覆盖。这不是 bug,是设计缺陷,上线后才暴露。


















