C# 条件弱表ConditionalWeakTable C#如何实现对象的动态附加属性

来源:这里教程网 时间:2026-02-21 17:40:58 作者:

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
)中。

相关推荐