写点什么

对 IDisposable 和静态分析的提议:DisposeUnused 属性

  • 2019-10-23
  • 本文字数:1734 字

    阅读完需:约 6 分钟

对IDisposable和静态分析的提议:DisposeUnused属性

当 .NET 初创的时候,关于IDisposable该如何使用存在一定的不确定性。结果,IDisposable的应用方式过于激进,许多种类的类都需要空的 Dispose 方法。这给静态分析工具带来了一些问题,它们无法将实际缺少Dispose调用与误报区分开来。


为了理解始末缘由,我们需要回过头看一下 CLR 早期的历史以及垃圾收集是如何运行的。最初,CLR 的目的在于是作为 Visual Basic 的新运行时,在 20 世纪 90 年代末 Visual Basic 是基于 COM 的。在 COM 模型下,对象会有一个引用计数。在引用创建和销毁的时候,引用计数会随之进行更新,如果计数变成零,对象就会被释放。这样的话,就形成了一个具有确定性的垃圾收集模型,在这种模型中,我们可以确切地知道该在什么时候清理资源。


引用方式的垃圾收集模型的最明显缺点就在于它很容易出现内存泄露。如果我们创建了一系列的对象,它们之间互相循环引用的话,每个对象都会让其他对象的引用计数无法降低到零,从而会出现内存泄露。在多线程环境中,它还会产生性能问题,因为在调整引用计数的时候,需要用到锁。


在开发的早期,微软决定采用标记-清理(mark-and-sweep)垃圾收集器来让 CLR 避免这些问题。随着 Java 的流行,这种方式在社区中得到了普遍的认可。但是,这种方式的 GC 并不能确定地释放资源,这使得它不适合用于数据库连接、文件处理和其他高度受限的资源。因此,IDisposable 应运而生。


与此同时,微软正在试验“组件”的理念。组件的概念从来没有被很好地定义。在这方面,有Component类,以及像IComponentIContainerISite这样的接口。在将近 20 年之后,文档中也只有一个很模糊的注释:“应用程序之间的对象共享”。其想法大概和 COM 类似,也就是某个应用可以直接与其他程序进行交互。但是,这并没有达到目的,所以被埋没在历史的故纸堆中了。


而在 Windows Forms 中,有一个不同的“组件”概念,它真正的意思是“可以放到表单/窗口(form/window)中的内容”。除了像文本框这样的实际 UI 元素之外,还包括添加了特定功能的对象,如计时器。这来自 VB 6 编程时代,当时几乎所有想使用的内容都必须要放到表单中。甚至数据库连接和命令也可以直接放到表单中。


这也就是为什么我们看到很多毫不相关的对象也标记成了IDisposable。像DataTableSqlCommand并没有要处理的非托管资源,但是在过去我们错误地认为最好将它们放到表单中,所以它们继承了Component类。而 Component 是 disposable 的,所以我们可以选择何时关闭代理对象。

静态分析

随着静态分析逐渐从高级工具变成了每个开发人员都该使用的工具,关于 disposable 对象不断增长的告警越来越成为一个问题。对于像SqlCommand这样短期存活的对象来说,这还不算太糟糕,因为可以很容易地使用 using 语句对其进行包装,而不需要考虑该语句实际上并没有执行任何操作。


DataTable这样的类就比较困难了。这是一个长期存活的对象,它所使用的地方可能距离创建它的地方非常远。除非设置为 suppressed 或禁用,否则静态分析工具将会报告 DataTable 和类似对象有未处理处理的告警和错误。

DisposeUnusedAttribute 提议

“最佳”方案是彻底移除所有无用的Dispose方法。但是,这并不是可行方案,因为这样会破坏向后的兼容性。


Edward Brey 提出了一个相当简单而优雅的解决方案。他建议创建一个DisposeUnused属性来屏蔽静态分析工具。子类不会继承此属性。


但是,这个设计也并非尽善尽美。一旦DisposeUnused用到了某个类上,移除它将会是破坏性的变更。对于 DataTable 来说,这并不是什么问题,不过,Stephen A. Imhoff 提供一个这样的样例。


这实际上会更糟糕,因为现在你可能会说“好的,忽略该契约,我就是这样声明的”,如果某个类型突然需要 dispose 某个资源的话(比如说,MemoryStream 要为大型数组或其他内容分配一个原生数组),那么你的消费者需要执行 dispose 操作,但是你之前却告诉人家不需要这样做……


另一个问题是你并不是总能知道变量里有什么。假设有一个类型为 Component 的变量。在编译时,我们无法判断放入变量的内容是否需要 dispose 处理。因此,更有意义的做法是只将DisposeUnused用到密闭类(sealed classed)中,这些类是无法子类化的。


原文链接:


A Proposal for IDisposable and Static Analysis: DisposeUnused Attribute


2019-10-23 08:001335

评论 1 条评论

发布
用户头像
对C#来说Attribute别翻译为属性更好些。记得王垠较早前就吐槽C#的这个了。
2019-10-23 17:52
回复
没有更多了
发现更多内容

11 张图 | 讲透原理,最细的 Eureka 增量拉取

悟空聊架构

悟空聊架构

四步轻松玩转微服务敏捷开发

阿里巴巴中间件

阿里云 微服务 云原生 敏捷开发 中间件

8 张图 | 剖析 Eureka 的首次同步注册表

悟空聊架构

Android C++系列:Linux进程间关系

轻口味

c++ android 28天写作 12月日更

产品对比:TeamCode DCS 与 Docker Dev Environment

Draven Gorden

云原生 团队协作 开发者工具 开发工具 开发环境

RocketMQ这样做,压测后性能提高30%

中间件兴趣圈

RocketMQ 性能 Apache RocketMQ

快速云原生化,从数据中心到云原生的迁移最佳实践

阿里巴巴云原生

阿里云 云原生 实践 迁云方案

优酷播放黑科技 | 自由视角技术体验优化实践

阿里巴巴终端技术

ios android 移动应用 音视频 客户端开发

架构实战营模块五作业

渐行渐远

架构实战营

模块5-微博评论高性能高可用计算架构分析

小何

「架构实战营」

车用能源的终极:氢能车落地普及还要多久?

脑极体

超基础的机器学习入门-原理篇

凹凸实验室

机器学习 AI 低代码平台

干货分享 | 深度解析云原生消息队列 AMQP

Apache Pulsar

架构 分布式 云原生 中间件 Apache Pulsar

vue框架的组件与组件通信方法

Changing Lin

12月日更

如何在信息不完备下进行快速决策?

石云升

决策 28天写作 职场经验 12月日更

一场关于元宇宙公司之死的剧本杀

脑极体

6000字 | 深入理解 Ribbon 的架构原理

悟空聊架构

悟空聊架构

未来,区块链将在这些领域广泛应用!

CECBC

开发者实践丨Agora Home AI 音视频的未来

声网

音视频 开发者实践 RTE大赛

博文推荐|多图科普 Apache Pulsar

Apache Pulsar

开源 架构 分布式 云原生 Apache Pulsar

Istio 实践手册 | 服务网格介绍

xcbeyond

istio 服务网格 28天写作 12月日更 Istio 实践手册

硬核图解 SpringCloud 源码系列

悟空聊架构

SpringCloud 悟空聊架构 内容合集 签约计划第二季 技术专题合集

AI:人工智能 or 异类智能(Alien Intelligence)

mtfelix

28天写作

模块一作业

Geek_e6f7f6

架构实战营

中年人的沉重 2

张老蔫

28天写作

32 K8S之DaemonSet/Job/CronJob控制器

穿过生命散发芬芳

k8s 28天写作 12月日更

资产数字化的当下,数据隐私危如累卵

CECBC

说出你和「云原生」的故事,获得年度云原生顶级盛会通行证

阿里巴巴云原生

阿里云 开源 云原生 投稿

iOS Pod Update 指数级变慢?看 Flutter 新一代仲裁算法 Pubgrub 如何解

阿里巴巴终端技术

flutter ios 算法 仲裁

Google 宣布将 Knative 捐赠给 CNCF

QiLab

Google Knative cncf

后端程序员福利套餐,22份资料合集,你能想到的关键技术,都在这里

奔着腾讯去

c++ golang Linux 音视频 学习资料

对IDisposable和静态分析的提议:DisposeUnused属性_语言 & 开发_Jonathan Allen_InfoQ精选文章