ConditionalWeakTable 是 .NET 中线程安全、弱引用键的映射容器,键被弱引用而值强引用,适合实现不阻碍对象回收的附加属性;典型用法是静态表映射 object 到 Dictionary,需用 GetOrCreateValue 避免并发重复创建。

ConditionalWeakTable 是什么,为什么适合附加属性
ConditionalWeakTable 是 .NET 提供的一个线程安全、弱引用的键值映射容器。它的核心特点是:键(TKey)被弱引用,当键对象被 GC 回收时,对应条目自动清理;而值(TValue)是强引用,但不会阻止键的回收。这正好满足“给任意对象动态挂载数据,且不阻碍其被回收”的需求——比用 Dictionary 安全,也比用 WeakReference 手动管理更可靠。
如何用 ConditionalWeakTable 实现附加属性
典型做法是定义一个静态 ConditionalWeakTable,把目标对象作为键,其附加属性集合作为值。每次访问前检查是否存在,不存在则新建并注册:
private static readonly ConditionalWeakTable
- 必须用
GetOrCreateValue,不能先TryGetValue再手动 new —— 否则并发下可能重复创建 - 值类型(如
int)会被装箱,但这是可接受的代价;若需高性能,可为常用类型单独定义泛型版本 - 键必须是引用类型(
object),不能传值类型(如int或结构体),否则会隐式装箱导致每次都是新实例,无法命中
和 WPF 的 DependencyProperty 对比有什么区别
WPF 的 DependencyProperty 是框架级基础设施,支持绑定、动画、继承、元数据等;而 ConditionalWeakTable 方案只是轻量级数据挂载,不参与任何生命周期或通知机制:
- 没有属性变更通知(
INotifyPropertyChanged不触发) - 不支持 XAML 绑定语法(如
{local:Attached.Prop}) - 无默认值、无验证回调、无强制转换逻辑
- 但可以用于非 UI 场景(如解析器上下文、ORM 实体增强、AOP 上下文透传)
容易踩的坑:GC 行为与调试可见性
最常被忽略的是:附加属性在调试器里看不到,也不会出现在对象监视窗口中,因为它是外部映射关系,不是对象成员字段。另外,GC 行为可能让人误判泄漏:
- 如果某个对象长期存活(比如被事件订阅、静态缓存持有),它的附加字典也会一直存在——这不是
ConditionalWeakTable的问题,而是你没释放持有者 - 调试时用
!dumpheap -stat查不到这些字典,得用!dumpheap -type Dictionary配合!gcroot追踪引用链 - 不要在
Finalize或析构函数里尝试读取附加属性——此时键对象已进入终结队列,ConditionalWeakTable可能已将其移除
真正难处理的是跨域/跨上下文场景:如果对象跨越了 AssemblyLoadContext 边界,而 ConditionalWeakTable 实例定义在某个上下文中,卸载该上下文后,表本身可能被回收,导致所有映射丢失——这时需要确保表实例驻留在中立上下文(如 Default)中。









