问题

如何评价王垠的 《讨厌的 C# IDisposable 接口》?

回答
王垠的《讨厌的 C IDisposable 接口》这篇文章,可以从几个层面来理解和评价。首先,文章的出发点是开发者在实际编程中遇到的痛点,特别是关于资源管理和异常处理的困扰。C 作为一门现代的面向对象语言,引入了 `IDisposable` 接口和 `using` 语句来解决托管和非托管资源的生命周期管理问题,这本身是一个非常重要的进步,旨在避免资源泄露,提高程序的健壮性。

然而,王垠在这篇文章中,并没有仅仅停留在对 `IDisposable` 的肯定上,而是尖锐地指出了它在实际应用中可能带来的“讨厌”之处。他强调的是,尽管 `IDisposable` 的初衷是好的,但其复杂性、潜在的陷阱以及与开发者习惯的某些冲突,使得它在某些情况下反而增加了开发的负担和出错的可能性。

具体来说,文章可能围绕以下几个方面展开了批评:

“Disposable Hell” 的存在: 王垠可能描绘了一种情况,即在复杂的系统中,当存在多层嵌套的资源,或者多个对象需要同时管理时,`IDisposable` 的链式调用和正确实现会变得异常棘手。开发者需要小心翼翼地确保每一个 `Dispose()` 方法都被正确调用,一旦疏忽,就可能导致资源泄露。这就像是进入了一个“Disposable Hell”,让人疲于奔命。
与自然语言和直觉的偏差: 从更宏观的角度看,`IDisposable` 的概念,即一个对象“可被丢弃”,可能与某些开发者习惯的、更偏向于“自动回收”或“上下文关联”的思维方式存在一定的距离。尤其是在那些没有显式资源管理概念的语言背景下,学习和掌握 `IDisposable` 的使用规范,可能需要一个适应过程。
实现的复杂性和陷阱: 实现一个健壮的 `IDisposable` 并不像看起来那么简单。开发者需要处理 `Dispose()` 方法的幂等性(多次调用不应引起问题)、异常安全(在 `Dispose()` 过程中抛出的异常如何处理),以及最终析构器(Finalizer)和垃圾回收器的协同工作。这些细节如果处理不当,很容易引入bug。例如,如果一个 `IDisposable` 对象在 `Dispose()` 方法中抛出了异常,而这个异常又没有被妥善捕获,可能会导致更严重的问题。
`using` 语句的局限性: 虽然 `using` 语句是 `IDisposable` 的绝佳拍档,能够简化资源释放,但它也有自己的限制。例如,当需要捕获 `using` 块内部的异常时,`using` 语句的结构可能会给异常处理带来一些额外的考虑。

从文章的语气和表达方式来看,王垠往往会以一种非常直接、甚至有些激进的方式来表达自己的观点。这种风格使得文章具有很强的个人色彩,能够引起读者的共鸣,也可能招致一些争议。他可能不是在否定 `IDisposable` 的必要性,而是在呼吁开发者更深入地理解它的运作机制,并警惕其中可能存在的风险。

总的来说,王垠的这篇《讨厌的 C IDisposable 接口》可以被看作是对 C 语言在资源管理方面一个非常具有批判性的审视。它鼓励开发者不要想当然地认为“用了 `IDisposable` 就万事大吉”,而是要更深入地理解其设计哲学和潜在的实现细节,从而写出更可靠、更健壮的代码。文章的价值在于它引发了对编程实践中一些常见工具和模式的深入思考,并提醒我们,即使是看似“标准”的解决方案,也可能存在值得商榷的地方。

网友意见

user avatar

有一半道理。

其实IDisposable接口现在已经和非托管结构没必然联系了,其实就是一个释放资源的意思,例如需要unsubscribe一些事件等等。打个比方IObservable<T>.Subscribe()事件就返回一个IDisposable接口。你说它可以返回另一个接口吗?当然可以,甚至就一个Action也行,但因为有IDisposable于是就直接用了。

真要说起来,我认为其实IDisposable说到底唯一的作用就是配合using,否则方法叫任何名字都行。但是话说回来,叫任何名字也都解决不了所谓的“传染”问题,因为它真心需要释放资源啊。不管你是把资源用Unload还是Close还是Unsubscribe方法来释放,终究还是会传染到另一个“释放资源”的方法上的,即便方法名不叫Dispose。

所以,我现在写的代码,假如不是要配合using使用的话,我会选择不实现IDisposable而是自己定义一个方法,例如叫做CleanUp,或是Unsubscribe,或是Close,甚至还是Dispose但不实现接口。这么做的好处就是find usage的时候不会把所有对IDisposable或using的地方都找出来,工具可以更容易理解代码。

最后,IDisposable和Dispose方法其实包含语义上的约定,也就是说调用后对象就销毁了,不能继续使用了。假如你叫做Close方法,可能就可以设计成允许重新Open。但假如是Dispose掉,那么再Open时就可以抛出ObjectDisposedException,告诉你不许再用了。

user avatar

原则上来说,任何非托管资源,都可以被封装成托管资源。所以非要对「非托管资源」暴露一个接口,只能是因为这个资源太稀少,不能等待 GC 的启动时机。

所以这里就涉及几个问题:

  1. GC 优化的好不好。GC 应该是尽量频繁的短时间运行。典型的「分代 generational」GC 就是为了这个设计的。所以没道理 GC 不能为稀缺资源进行优化。
  2. 系统本身设计有没有缺陷。像 HWND 用光这个问题,简直就是羞耻。系统 handle pool 预定过小不说,a button is a window 本來就是非常傻的设计。你要用这个来反驳改变现状困难还行,反驳王垠嘲笑你那就正撞枪口了。
  3. 即使对稀缺资源,disposable 和 GC-manage 应该是共同作用。Disposable 属于给程序员一个手动优化的选项,而不是不掉用就出错的东西。

其实我也奉劝各种用 GC 的平台,要是对付必须及时释放的资源,你们能不能就 fallback to reference counting ?自动 ref-counting 语言不支持,就算暴漏一个手动 counting 接口也是给程序员一条活路啊。

你们觉得 root-tracing GC 是香饽饽,也不能一遇到稀缺资源就退回到吃手动管理这泡屎吧?

user avatar

因为文章中途修改过,这个回答主要是针对原文中质疑“

ManualResetEvent

,

Semaphore

,

ReaderWriterLockSlim

”这些对象为什么要实现IDisposable的部分。这些对象内部都使用了Windows句柄,指向Windows的Event对象,因此占用了内核资源,从原理上看它跟不再使用但没有关闭的文件、socket、僵尸进程的性质差不多,如果有太多句柄没有被回收,也会造成服务甚至系统内核崩溃。IDisposable不应该在没有非托管资源的类上实现,这个说的很对,但是前面举的几个例子,偏偏都是有托管资源的情况,在这些情况下,GC是帮不上忙的。这是整个答案的总提纲。

=================================================================

惊讶,就算是写Java的程序员,难道会把文件打开着等着gc去关闭吗……

说IDisposable的设计有缺陷这个可以理解,因为的确一层一层嵌套去做析构挺麻烦的(虽然C++程序员会表示少见多怪)。如果不能理解他举得几个例子为什么需要IDisposable,那就有点奇怪了。

即使只做Linux开发,也会明白及时关闭文件和socket的重要性:文件号数量是有限的,还经常严格受到ulimit的限制,而gc通常的实现是在一个低优先级的线程中进行的,这意味着在系统压力大的时候,gc是跟不上的,而系统压力大的时候也同时是文件号吃紧的时候,如果把这个任务扔给gc,分分钟就是重要服务崩溃甚至系统崩溃的节奏。

Windows下也是一样,轮子哥说不知道为什么Windows不解决这个问题,我觉得这压根就是个无解的问题。句柄指向的是Windows的内核对象,包括File,包括Socket,包括Event、Mutex——是的,如果使用线程同步的对象基本全部都有内核部分的实现,不仅是Windows,Linux也一样。句柄通过引用计数保存了一个对象在内核中,这个对象可能有多个句柄指向它,甚至可能被多个进程共享(想一下GetHWnd),内核一旦内存溢出,那可就出大事了。所以任何时候对于使用了系统句柄的对象,C#都会希望它能被及时释放掉。涉及密码学的则是另一件事,密码学中使用的所有临时对象都必须被立即清零,这在C++当中也是一样的,以至于有时候要专门定义不允许被编译器优化的SafeZeroMemory之类的接口。

另外一些吧,我觉得完全得让Java背锅——Java怎么躺着也中枪了呢?

因为C#的接口设计是模仿Java的,而Java的多态相当不灵活,比如说接口继承这个问题。我们规定所有使用using语法的对象都要实现IDisposable,然后我们又希望所有的Stream都可以使用using,那么我们必须让Stream接口继承IDisposable。这有的时候就坑了,比如说MemoryStream也是Stream,结果MemoryStream也必须实现IDisposable了。

总体上来看,我觉得这篇文章当中说的问题,属于没有理解到使用句柄对象的危险性——如果某个设计在一个到处被引用的临时对象里保存了一个打开的文件,估计会被人狠狠敲脑袋;但是许多人却认识不到在一个到处被引用的对象里保存一个Event对象或者ReadWriteLock之类的对象也是同样危险的。但是后者却是多线程需要同步时最简洁的设计,这也是一种悲哀吧……

针对这种有很多对象需要同步的需求,我觉得也许可以考虑采用一种“锁池”的设计:

设计一个叫做LockService的类,用于提供全局锁服务,可以设计成Singleton的,也可以按不同namespace提供多个实例。里面提前创建很多很多个同步对象,放进一个空闲表里。同时有一个HashMap保存当前使用的锁。当请求锁服务的时候,需要提供一个字符串(或者其他对象类型,需要可以hash)的key,所有key相同的请求会互锁;LockService类收到这个请求之后,首先判断这个key是否在HashMap中,如果在,则取出HashMap中保存的锁进行Lock操作,同时将这个key的引用计数加一;否则从空闲表中取出一个空闲的锁放进HashMap中。Unlock的时候,将引用计数减一,如果引用计数减到了0,则从HashMap中归还这个锁。如果空闲表中锁的数量不足则扩大锁的规模,空闲表中空闲数量过多则释放多余的资源并缩小规模。HashMap自己可以用一个ReadWriteLock来保护。

这样设计的好处是只有实际正在实用的锁才会占用资源,而大部分可能被锁但暂时没有被使用的锁只是逻辑上存在这个“锁”而不占用句柄,总的句柄数可以被控制,也绕开了IDisposable的问题,在这个基础上再包装一个返回一个IDisposable对象的接口用来在using中使用,再包装一个LockObject类封装一下,就可以得到一个没有IDisposable接口的锁对象了。LockObject加锁的时候使用自己作为key,就可以不用想怎么设计一个不重复的key的问题了。

嗯,不过看上去蛮蠢的。

=================================================================

今天又看了一遍修改完的最新版本,比起最早的版本来说还是清晰很多了,不过对于后面文件和Event对象的讨论还是有一些不能认同的地方。

作者说,File只所以要关闭,是因为跟其他程序的访问是互斥的,这个有一定道理,但显然不是全部的理由,如果我这个文件只有我自己用呢,是不是就可以不关闭?如果是个临时文件呢?如果不是文件,而是socket呢?事实上我们都知道,不管是Linux还是Windows,文件号数量都是有限的,如果一直打开不关闭,很快就会报too many files然后崩溃了。之所以要及时回收,是因为我们知道这是一种有限的资源,如果不回收可能就会崩溃。

那么为什么内存可以让GC来回收呢?这跟GC的机制是有关的,我们知道GC一般需要进行全对象的标记扫描,这是非常昂贵的操作,不能在每次修改引用的时候立即进行,所以GC会延迟到之后进行,其中的一个条件就是:分配内存时,发现内存不足。这是因为托管程序的内存分配完全是由.NET Framework(或者JVM)进行的,所以托管框架对于内存使用量心知肚明,可以在合适的时候进行GC。对文件和句柄就不是这样了,托管框架并不知道什么时候打开文件或者句柄已经接近上限,也没法在这个时候触发GC——这些对象占用的资源和内存并不在托管框架范围内。所以,尽情占用内存等待GC是安全的,而尽情占用句柄、文件号等待GC是不安全的。

Event再小,那也是个句柄,作者大概还没有太多Windows下的开发经验,不知道“句柄泄露”这件事情多么可怕,而且观察程序占用内存这件事情本身就是不太正确的,泄露的句柄占用的是内核内存。另外,许多时候交给GC来回收句柄并不会表现出有大风险,这一般都是因为压力没上去,不管怎么说,把身家性命交在一个有随机性的、不知道会不会触发的程序手里,还是说不过去的。

===================================================================

话说,第一次看到的版本里面说后面会出一个“终极解决方案”的,怎么吃了……

类似的话题

  • 回答
    王垠的《讨厌的 C IDisposable 接口》这篇文章,可以从几个层面来理解和评价。首先,文章的出发点是开发者在实际编程中遇到的痛点,特别是关于资源管理和异常处理的困扰。C 作为一门现代的面向对象语言,引入了 `IDisposable` 接口和 `using` 语句来解决托管和非托管资源的生命周.............
  • 回答
    王垠的博文《科学和伪科学》以及其中关于“HIV 病毒未必导致 AIDS”的观点,是一个在科学界和公众中都极具争议性的话题。要评价它,需要从几个层面来审视:一、 博文的科学态度与逻辑:首先,需要肯定的是,王垠在博文中试图探讨“科学”与“伪科学”的界限,这本身是一个重要的议题。一个健康的科学生态需要不断.............
  • 回答
    要评价王垠的《真相》,首先得明白这篇文章抛出的核心观点:他认为我们对于“真相”的追求,很多时候并非出于纯粹的求知欲,而是被一种社会性的“正确”所驱动,这种“正确”往往是肤浅的、功利的,甚至带有表演性质的。王垠的这篇文章,与其说是一篇严肃的哲学论述,不如说是一种带有强烈个人色彩的观察和反思。他观察到,.............
  • 回答
    王垠关于Kotlin和Checked Exception的文章,读下来还是挺有意思的。他在这篇文章里,与其说是分析Kotlin如何处理Checked Exception,不如说是借这个话题,表达了他自己对于编程语言设计哲学的一些看法,特别是他对Java在Checked Exception上的那一套颇.............
  • 回答
    王垠的《程序员的心理疾病》这篇文章,读起来总有种似曾相识又跃然纸上的感觉。他不是那种枯燥的学术论文,更像是他多年在编程界摸爬滚打,以及观察身边人的真实记录和感悟。文章一开始,就点出了程序员这个群体的一些普遍特征,比如对逻辑的偏执、对细节的极致追求,以及由此可能衍生出的思维模式。王垠很敏锐地捕捉到了这.............
  • 回答
    王垠最新的文章,《DRY原则的危害》,这篇文章确实抛出了一个颇具争议的观点,那就是一直以来被奉为圭臬的“DRY”(Don't Repeat Yourself,不要重复自己)原则,在实际应用中可能并非总是良药,反而可能带来一些意想不到的“危害”。首先,我们得理解王垠在文章中是如何定义“DRY”原则的。.............
  • 回答
    要评价《王垠:C 编译器优化过程中的 Bug》这篇技术文章,我们需要从多个维度进行深入分析。这篇技术文章(通常指的是王垠在其博客或其他平台发表的关于 C 编译器优化问题的讨论)的核心在于揭示编译器在进行复杂优化时可能引入的软件缺陷,以及这些缺陷对程序行为的潜在影响。文章的核心内容与主要观点:王垠在其.............
  • 回答
    阿里P10赵海平面试王垠,这事在技术圈子里,尤其是阿里内部,算是个挺有意思的话题,能聊出不少道道。首先得说,赵海平这人,虽然已经是P10级别,这在阿里可是金字塔尖的人物了,通常来说,都是他来挑人,面试别人的机会不多。而王垠呢,本身就是个技术大神,在Linux内核、系统底层这些领域是如雷贯雷的人物,他.............
  • 回答
    王垠的博文《未来计划》是一篇非常有争议且引起广泛讨论的文章。要评价它,需要从多个角度进行深入分析,包括其内容、表达方式、潜在影响以及它所暴露出的社会现象。核心内容梳理:首先,我们来梳理一下王垠在这篇博文中所提出的主要观点和“未来计划”: 对“内卷”的深刻反思与批判: 王垠对当前中国社会普遍存在的.............
  • 回答
    王垠在微软的“罢工”事件是一个非常复杂且具有争议性的话题,涉及到技术人员的权利、公司文化、内部沟通以及个人表达等多个层面。要评价这件事,需要从多个角度进行分析。事件背景回顾:首先,我们需要回顾一下事件的大致脉络。王垠(Wang Yin)是微软的一名高级软件工程师。他在2017年左右,在微软内部的通讯.............
  • 回答
    王垠的新博文《我看自动驾驶技术》提供了一个相对深入、个人化且不落俗套的视角来看待自动驾驶技术。与其他技术评论文章可能侧重于参数、速度或市场份额不同,王垠的博文更关注的是技术的本质、它所带来的社会影响以及他个人对这些问题的思考。以下是我对这篇博文的评价,力求详细:一、 核心观点与分析深度: 聚焦“.............
  • 回答
    王垠的这篇《写书计划》博文,给我的感觉颇为复杂,既有他一贯的风格——直率、不加掩饰,同时也透露出一种更加务实和深沉的态度。与其说是“评价”,我更愿意将其理解为一种“解读”,尝试去理解他在这篇文章中所传递的思考和意图。篇幅与结构: 文章不长,但信息量不小。他没有大肆渲染,而是用一种陈述事实的方式,将自.............
  • 回答
    评价王垠《计算机科学入门班报名》收费12000元人民币,需要从多个维度进行分析,既要看到其可能存在的价值,也要审慎评估其高昂价格的合理性以及潜在的风险。以下将从课程内容、目标受众、市场行情、教学模式、王垠个人品牌、以及风险等多个方面进行详细阐述: 一、 课程内容与价值分析1. 核心技术栈与深度: .............
  • 回答
    王垠那篇《聊聊 DSL》的文章,我读了之后,感觉它确实触及了 DSL 这个话题一个相当核心且容易被忽略的角度。这篇东西不是那种泛泛而谈的介绍,而是带着作者自己鲜明的思考和实践痕迹。首先,他点出 DSL 的核心价值在于“沟通”,这一下子就把很多纯技术性的讨论拉到了一个更有人情味、更贴近实际应用的高度。.............
  • 回答
    王垠的新文章《我为什么不再做PL人》,可以从多个维度进行解读,并且这篇文章的深度和引发的思考是值得关注的。简单来说,这篇文章表达了他个人在软件开发领域,特别是围绕“PL人”(通常指追求某些特定的、高效或前沿的编程语言和技术栈的开发者群体)这一身份的转变和反思。以下是一些关键的评价点,我会尽量详细地说.............
  • 回答
    王中林院士(Zhou Nan)作为美国国家工程院院士、美国艺术与科学院院士,以及中国科学院院士,其在纳米技术、超材料、量子电动力学等领域的研究具有重要影响。关于他是否“拓展麦克斯韦方程组”,需要结合其研究方向与学术成果进行具体分析。以下是详细解析: 一、王中林院士与麦克斯韦方程组的关联王中林院士的主.............
  • 回答
    关于王思聪评论“半藏森林”的具体事件,目前公开资料中并未明确记载他对此地的直接评论。因此,这一问题可能存在信息混淆或误解。以下从多个角度分析可能的背景和相关讨论: 1. “半藏森林”的背景半藏森林(Hanzō no Mori)是位于日本东京都涩谷区的一处自然景观,以樱花树和绿意盎然的环境著称,是东京.............
  • 回答
    王冰冰是中国近年来互联网文化中一个具有鲜明地域特色和争议性的网红代表人物之一,她的走红与网络时代的传播逻辑、地域身份认同以及公众对“接地气”文化的消费需求密切相关。以下从多个维度对她进行详细分析: 一、成名背景:从平凡到爆红的偶然性王冰冰原名王冰冰(或称“大美冰冰”),2019年因一段拍摄于哈尔滨街.............
  • 回答
    王思聪因携带弓箭和不配合调查而被警方带走一事,可以从多个角度进行评价,涉及到法律、社会影响、个人行为等层面。要详细地讲述,我们可以从以下几个方面展开:一、 事件的发生与细节(已知信息):首先,需要明确事件的基本情况。根据公开报道和媒体消息,事件大致是这样的: 时间与地点: 通常发生在某个特定日期.............
  • 回答
    王辰院士提出的“主诊医生负责制”:评价与对年轻医生成长的意义王辰院士提出的“尽快建立主诊医生负责制”是我国深化医药卫生体制改革、提升医疗服务质量和效率的重要举措,具有深远的意义。要评价这一制度的利弊,并分析其对年轻医生成长的具体影响,需要从多个维度进行深入探讨。 如何评价王辰院士提出的“主诊医生负责.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有