当前位置: 首页 > news >正文

.NET/C# 使用 ConditionalWeakTable 附加字段(CLR 版本的附加属性,也可用用来当作弱引用字典 WeakDictionary)

如果你使用过 WPF/UWP 等 XAML UI 框架,那么应该了解到附加属性的概念。那么没有依赖属性支持的时候如何做附加属性的功能呢?你可能会想到弱引用。但这需要做一个弱引用字典,要写的代码还是非常麻烦的。

本文介绍 .NET 的 ConditionalWeakTable<TKey,TValue> 类型,适用于 .NET Framework 4.0 以上和全部 .NET Core 的版本。


本文内容

    • 这不是字典
    • 验证

这不是字典

现成可用的弱引用字典,即 ConditionalWeakTable<TKey,TValue>。然而实际上这个类的原本作用并不是当作字典使用!

如果你使用过 WPF/UWP 等 XAML UI 框架,那么应该了解到附加属性的概念。这其实是 .NET 为我们提供的一种附加字段的机制。

比如你有一个类:

class Foo
{
    // 请忽略这里公有字段带来的设计问题,只是为了演示。
    public string A;
}

我们希望为它增加一个字段 Bar

class Foo
{
    public string A;
    public Bar Bar;
}

那么我们需要修改类 Foo 本身以实现这个效果;但是这样就使得 Foo 耦合了 Bar,从而破坏了内聚性/依赖倒置原则。典型的情况是 Foo 类表示一个人 Person,它里面不应该包含一个 某行账号 这样的字段,因为很多人是没有那家银行账号的。这个信息让那家银行存起来才是比较符合设计原则的设计。

我们可以通过一个字典 Dictionary<Foo, Bar> 来存储所有 Foo 实例额外增加的 Bar 的值可以避免让 Foo 类中增加 Bar 字段从而获得更好的设计。但这样就引入了一个静态字典从而使得所有的 FooBar 的实例无法得到释放。我们想当然希望拥有一个弱引用字典来解决问题。然而这是一个 X-Y 问题。

实际上 .NET 中提供了 ConditionalWeakTable<TKey,TValue> 帮我们解决了最本质的问题——在部分场景下期望为 Foo 类添加一个字段。虽然它不是弱引用字典,但能解决此类问题,同时也能当作一个弱引用字典来使用,仅此而已。

你需要注意的是,ConditionalWeakTable<TKey,TValue> 并不实现 IDictionary<TKey,TValue> 接口,只是里面有一些像 IDictionary<TKey, TValue> 的方法,可以当作字典使用,也可以遍历取出剩下的所有值。

验证

ConditionalWeakTable<TKey,TValue> 中的所有 Key 和所有的 Value 都是弱引用的,并且会在其 Key 被回收或者 Key 和 Value 都被回收之后自动从集合中消失。这意味着当你使用它来为一个类型附加一些字段或者属性的时候完全不用担心内存泄漏的问题。

下面我写了一段代码用于验证其内存泄漏问题:

  1. ConditionalWeakTable<TKey,TValue> 中添加了三个键值对;
  2. 将后两个的 key 设为 null
  3. 进行垃圾回收。

{% raw %}

using System;
using System.Linq;
using System.Runtime.CompilerServices;

namespace Walterlv.Demo.Weak
{
    class Program
    {
        public static void Main()
        {
            var key1 = new Key("Key1");
            var key2 = new Key("Key2");
            var key3 = new Key("Key3");

            var table = new ConditionalWeakTable<Key, WalterlvValue>
            {
                {key1, new WalterlvValue()},
                {key2, new WalterlvValue()},
                {key3, new WalterlvValue()}
            };

            var weak2 = new WeakReference(key2);
            key2 = null;
            key3 = null;

            GC.Collect();

            Console.WriteLine($@"key1 = {key1?.ToString() ?? "null"}
key2 = {key2?.ToString() ?? "null"}, weak2 = {weak2.Target ?? "null"}
key3 = {key3?.ToString() ?? "null"}
Table = {{{string.Join(", ", table.Select(x => $"{x.Key} = {x.Value}"))}}}");
        }
    }

    public class Key
    {
        private readonly string _name;
        public Key(string name) => _name = name;
        public override string ToString() => _name;
    }

    public class WalterlvValue
    {
        public DateTime CreationTime = DateTime.Now;
        public override string ToString() => CreationTime.ToShortTimeString();
    }
}

{% endraw %}

这段代码的运行结果如下图:

运行结果

从中我们可以发现:

  1. 当某个 Key 被回收后,ConditionalWeakTable<TKey,TValue> 中就没有那一项键值对了;
  2. 当 Key 的实例依然在的时候,ConditionalWeakTable<TKey,TValue> 中的 Value 依然还会存在。

另外,我们这里在调查内存泄漏问题,你需要在 Release 配置下执行此代码才能得到最符合预期的结果。


参考资料

  • ConditionalWeakTable<TKey,TValue> Class (System.Runtime.CompilerServices) - Microsoft Docs
  • Good implementation of weak dictionary in .Net - Stack Overflow
  • Presenting WeakDictionary[TKey, TValue] – Nick Guerrera’s blog
  • .net - Understanding ConditionalWeakTable - Stack Overflow

我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

相关文章:

  • 在 Visual Studio 中重新将高级保存功能放出来,便于强制指定文件编码格式
  • 在整个 Git 仓库的历史(包括所有分支和标签)中修改提交作者的信息(姓名和邮箱)
  • .NET/C# 避免调试器不小心提前计算本应延迟计算的值
  • .NET/C# 编译期能确定的字符串会在字符串暂存池中不会被 GC 垃圾回收掉
  • .NET/C# 的字符串暂存池
  • 如何为 Win32 的打开和保存对话框编写文件过滤器(Filter)
  • C# 中新增类型的命名空间只需部分与其他命名空间名称相同即可破坏源码兼容性
  • 使用 ProcessMonitor 找到进程所操作的文件的路径
  • MSBuild 在编写编译任务的时候判断当前是否在 Visual Studio 中编译
  • git 配置错误导致无法推送远端仓库?本文介绍各种修复方式
  • WPF 很少人知道的科技
  • WPF 程序的编译过程
  • 制作通过 NuGet 分发的源代码包时,如果目标项目是 WPF 则会出现一些问题(探索篇,含解决方案)
  • .NET 的程序集加载上下文
  • .NET 使用 ILMerge 合并多个程序集,避免引入额外的依赖
  • ABAP的include关键字,Java的import, C的include和C4C ABSL 的import比较
  • CentOS7简单部署NFS
  • Consul Config 使用Git做版本控制的实现
  • ES6系统学习----从Apollo Client看解构赋值
  • Java读取Properties文件的六种方法
  • Linux Process Manage
  • PHP的类修饰符与访问修饰符
  • Redis的resp协议
  • Sass Day-01
  • 阿里研究院入选中国企业智库系统影响力榜
  • 从零搭建Koa2 Server
  • 多线程 start 和 run 方法到底有什么区别?
  • 力扣(LeetCode)357
  • 浅谈Golang中select的用法
  • 如何在GitHub上创建个人博客
  • 使用iElevator.js模拟segmentfault的文章标题导航
  • 算法-图和图算法
  • 微信小程序实战练习(仿五洲到家微信版)
  • 为物联网而生:高性能时间序列数据库HiTSDB商业化首发!
  • 移动端 h5开发相关内容总结(三)
  • 原生js练习题---第五课
  • 3月7日云栖精选夜读 | RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势 ...
  • postgresql行列转换函数
  • 整理一些计算机基础知识!
  • # SpringBoot 如何让指定的Bean先加载
  • ###51单片机学习(1)-----单片机烧录软件的使用,以及如何建立一个工程项目
  • ###项目技术发展史
  • (SERIES10)DM逻辑备份还原
  • (草履虫都可以看懂的)PyQt子窗口向主窗口传递参数,主窗口接收子窗口信号、参数。
  • (二)构建dubbo分布式平台-平台功能导图
  • (仿QQ聊天消息列表加载)wp7 listbox 列表项逐一加载的一种实现方式,以及加入渐显动画...
  • (十一)手动添加用户和文件的特殊权限
  • (一)项目实践-利用Appdesigner制作目标跟踪仿真软件
  • (转)Sublime Text3配置Lua运行环境
  • (转)甲方乙方——赵民谈找工作
  • .NET 4 并行(多核)“.NET研究”编程系列之二 从Task开始
  • .net core 的缓存方案
  • .net on S60 ---- Net60 1.1发布 支持VS2008以及新的特性
  • .NET 中 GetProcess 相关方法的性能
  • .NET3.5下用Lambda简化跨线程访问窗体控件,避免繁复的delegate,Invoke(转)