weakreference 仅防止gc阻止对象回收,不适用于自动缓存;应优先用类型安全、无装箱的weakreference,配合trygettarget避免nre,并采用getorcreate模式管理缓存生命周期。

WeakReference 不是用来“自动管理缓存”的工具,它只负责一件事:让你持有对象引用,又不阻止 GC 回收。直接拿它当缓存用,十有八九会掉进 NullReferenceException 或逻辑跳变的坑里。
WeakReference 和 WeakReference 到底选哪个
优先用 WeakReference<t></t>,.NET 4.5+ 全支持,类型安全、无装箱、编译期能拦住类型误用。
-
WeakReference的Target是object,每次取值都要as T或强制转换;一旦Target == null还没判空就调方法,立刻崩 -
WeakReference<t></t>提供TryGetTarget(out T target),返回bool+ 输出参数,天然规避 NRE,也省去空检查的冗余分支 - 值类型(如
int、DateTime)用非泛型版会触发装箱;泛型版完全避免这点,且TryGetTarget返回的就是原生类型
缓存场景下最常踩的三个空引用坑
弱引用缓存不是“自动续命”,它是“随时可能断联”。错误写法集中在访问模式上:
- 直接链式调用:
weakRef.Target.SomeMethod()——Target可能在取值瞬间被 GC 回收,多线程下尤其危险 - 缓存
Target到局部变量后反复用:var obj = weakRef.Target; obj.DoA(); obj.DoB();—— 第二个调用时obj可能已为null - 把
IsAlive当成“安全开关”:if (weakRef.IsAlive) { weakRef.Target.DoWork(); }——IsAlive和Target读取不是原子操作,中间照样可能被回收
WeakReference 缓存必须配合 GetOrCreate 模式
单靠 WeakReference 做缓存毫无意义,它不负责重建。真正可用的缓存逻辑必须是“获取或创建”闭环:
- 先查字典(如
Dictionary<string weakreference>></string>),再调TryGetTarget;失败就调Func<t></t>创建新实例 - 键建议用业务标识(如文件路径、
typeof(T)),避免用随机对象当 key 导致哈希冲突或泄漏 - 这个模式本身线程不安全,高并发下需加锁或换
ConcurrentDictionary,但注意锁粒度——别整个字典一把锁,否则性能反降 - 已回收的条目应主动从集合中移除,否则
TryGetTarget总返回false,字典会越积越大
WeakReference 对小对象没意义,也破不开所有循环引用
WeakReference 自身有开销,对小对象(比如 int 包装类、短字符串、轻量 DTO)来说,开销可能比缓存对象还重。
- 它无法替代
IDisposable或事件解绑:文件句柄、数据库连接、Timer、GDI 对象等,仍必须显式调用Dispose()或Close() - 事件订阅引发的泄漏,本质是委托强持有了订阅者,仅靠
WeakReference绑定发布者没用;得用弱事件模式或手动解绑 - 终结器(
Finalize)里访问Target是未定义行为,TryGetTarget必然返回false
真正难的不是写对那一行 TryGetTarget,而是把“对象随时可能消失”这个事实,贯穿到整个调用链的设计里——比如 UI 更新、异步回调、生命周期钩子,都得默认它已失效,而不是靠一次判断保全程安全。











