ConditionalWeakTable 是什么,为什么适合附加属性
ConditionalWeakTable<tkey tvalue></tkey>是 .NET 提供的一个线程安全、弱引用的键值映射容器。它的核心特点是:键(
TKey)被弱引用,当键对象被 GC 回收时,对应条目自动清理;而值(
TValue)是强引用,但不会阻止键的回收。这正好满足“给任意对象动态挂载数据,且不阻碍其被回收”的需求——比用
Dictionary<object object></object>安全,也比用
WeakReference手动管理更可靠。
如何用 ConditionalWeakTable 实现附加属性
典型做法是定义一个静态
ConditionalWeakTable<object dictionary object>></object>,把目标对象作为键,其附加属性集合作为值。每次访问前检查是否存在,不存在则新建并注册:
private static readonly ConditionalWeakTable<object, Dictionary<string, object>> _attachedProps =
new ConditionalWeakTable<object, Dictionary<string, object>>();
public static void SetAttachedProperty(object target, string key, object value)
{
var dict = _attachedProps.GetOrCreateValue(target);
dict[key] = value;
}
public static bool TryGetAttachedProperty(object target, string key, out object value)
{
if (_attachedProps.TryGetValue(target, out var dict) && dict?.TryGetValue(key, out value) == true)
return true;
value = null;
return false;
}
必须用 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)中。
