为啥 .NET 中没有 RAII?

Posted

技术标签:

【中文标题】为啥 .NET 中没有 RAII?【英文标题】:Why is there no RAII in .NET?为什么 .NET 中没有 RAII? 【发布时间】:2010-09-15 11:28:45 【问题描述】:

作为主要的 C++ 开发人员,Java 和 .NET 中没有 RAII (Resource Acquisition Is Initialization) 一直困扰着我。清理的责任从类编写者转移到其消费者(通过try finally 或.NET 的using construct)这一事实似乎明显逊色。

我明白为什么在 Java 中不支持 RAII,因为所有对象都位于堆上,而垃圾收集器本身不支持确定性销毁,但在 .NET 中引入了值类型 (struct)我们有(看似)完美的 RAII 候选人。在堆栈上创建的值类型具有明确定义的范围,并且可以使用 C++ 析构函数语义。但是 CLR 不允许值类型具有析构函数。

我的随机搜索发现了一个论点,即如果值类型是boxed,它属于垃圾收集器的管辖范围,因此它的销毁变得不确定。 我觉得这个论点不够有力,RAII 的好处大到可以说一个带有析构函数的值类型不能装箱(或用作类成员)。

长话短说,我的问题是:是否还有其他原因不能使用值类型来将 RAII 引入 .NET? (或者你认为我关于 RAII 明显优势的论点有缺陷吗?)

编辑:我肯定没有清楚地表达这个问题,因为前四个答案没有抓住重点。我知道 Finalize 及其非确定性特征,我知道 using 构造,我觉得这两个选项不如 RAII。 using 是类消费者必须记住的另一件事(有多少人忘记将 StreamReader 放入 using 块中?)。我的问题是关于语言设计的哲学问题,为什么会这样?可以改进吗?

例如,对于通用的确定性可破坏值类型,我可以使 usinglock 关键字变得多余(可通过库类实现):

    public struct Disposer<T> where T : IDisposable
    
        T val;
        public Disposer(T t)  val = t; 
        public T Value  get  return val;  
        ~Disposer()  // Currently illegal 
        
            if (val != default(T))
                val.Dispose();
        
    

我不禁以我曾经看过但目前无法找到其来源的恰如其分的报价作为结尾。

当我冰冷的死手超出范围时,您可以承担我的确定性破坏。 --匿名

【问题讨论】:

【参考方案1】:

更好的标题是“为什么 C#/VB 中没有 RAII”。 C++/CLI(托管 C++ 的堕胎演变)具有与 C++ 完全相同的 RAII。这只是其他 CLI 语言使用的相同终结模式的语法糖(C++/CLI 的托管对象中的析构函数实际上是终结器),但它就在那里。

你可能会喜欢http://blogs.msdn.com/hsutter/archive/2004/07/31/203137.aspx

【讨论】:

好点是问题仅与 C#/VB 有关,但 C++/CLI 析构函数不是终结器。他们实现IDisposable。它是 C++/CLI 堆栈语义语法的特殊之处,因为它允许您编写统一代码来处理任何生命周期有限的对象,无论它是否实现 IDisposable,这在泛型代码中特别有用。 链接断开,再次:(【参考方案2】:

很好的问题,也让我非常困扰。似乎人们对 RAII 的好处的看法截然不同。根据我使用 .NET 的经验,缺乏确定性(或至少是可靠的)资源收集是主要缺点之一。事实上,.NET 多次迫使我使用整个架构来处理可能(但可能不需要)需要显式收集的非托管资源。当然,这是一个巨大的缺点,因为它使整体架构变得更加困难,并将客户的注意力从更中心的方面转移开。

【讨论】:

【参考方案3】:

Brian Harry 有一篇关于基本原理的好帖子 here。

摘录如下:

确定性终结和值类型(结构)呢?

------------- 我已经看到很多关于结构的问题 析构函数等。这是值得的 评论。有多种 为什么有些语言没有的问题 拥有它们。

(1) 作文 - 他们不给你 一般的确定性寿命 同种组合的情况 上述原因。任何 包含一个的非确定性类 在它之前不会调用析构函数 还是被 GC 敲定了。

(2) 复制构造函数 - The one place 真的很好的地方在 堆栈分配的局部变量。他们将是 范围为该方法,所有将是 伟大的。不幸的是,为了得到 这真的有效,你还必须 添加复制构造函数并调用它们 每次复制实例时。 这是最丑最丑的之一 关于 C++ 的复杂事情。你最终 让代码在各处执行 你没想到的地方。它 导致一堆语言问题。 一些语言设计者选择 远离这个。

假设我们创建了结构体 析构函数,但添加了一堆 限制他们的行为 明智地面对问题 更多。限制将是 类似:

(1) 您只能将它们声明为本地 变量。

(2) 你只能通过它们 参考文献

(3) 你不能分配它们,你 只能访问字段并调用 方法。

(4) 你不能装箱 他们。

(5) 使用它们的问题 反射(后期绑定),因为 通常涉及拳击。

也许更多, 但这是一个好的开始。

这些东西有什么用?将 你实际上创建了一个文件或一个 数据库连接类,可以 只能用作局部变量?一世 相信没有人真的会。 你会做的是创建一个 通用连接,然后 为 用作作用域的局部变量。这 然后来电者会选择他们 想用。注意调用者做了一个 决定,并不完全 封装在对象本身中。 鉴于您可以使用某些东西 喜欢提出的建议 几个部分。

.NET 中 RAII 的替代品是 using-pattern,一旦您习惯了它,它几乎同样有效。

【讨论】:

除了 'using' 语句如果你将它用于 /everything/ 会变得笨拙,这与 RAII 不同。 而布赖恩·哈里错了——这些问题中的许多都是可以解决的;例如,参见 boost 的指针容器。当然,这需要一些扎实的锻炼,但它是完全可以解决的。就此而言,几乎每个 .NET 架构都意识到您需要所有权概念才能与 IDisposable 一起使用 - 如果您需要它,您不妨将它嵌入到语言中。解决方案可能很混乱,但比 Java + .NET 采取的这种把你的头埋在沙子里然后让它走开的方法更好。 这样的结构在某些情况下可能非常有用,如果它们可以实现到其他类型的隐式转换运算符则更是如此。例如,Fluent 接口中的一个问题是每个WithXX 方法都必须返回一个新对象,即使对其目标的引用在其完成后也不会继续存在。如果WithXX 方法可以返回“不可复制”结构,则该结构的WithXX 方法可以安全地改变他们持有引用的东西,而不必先复制它。 @Arafangion,是否有任何静态分析让您知道您忘记在 IDisposable 上包含“使用”语句? @JoelFan 可能。距离我上次看这个已经十多年了。 :)【参考方案4】:

您最接近的是非常有限的 stackalloc 运算符。

【讨论】:

【参考方案5】:

如果您搜索它们,则有一些类似的线程,但基本上归结为,如果您想在 .NET 上进行 RAII,只需实现 IDisposable 类型并使用“using”语句来获得确定性的 Disposal。这样一来,许多相同的理念就可以以稍微冗长的方式实现和使用。

【讨论】:

【参考方案6】:

恕我直言,VB.net 和 C# 需要的主要内容是:

    字段的“使用”声明,这将导致编译器生成代码来处理所有标记的字段。编译器的默认行为应该是让一个类实现 IDisposable(如果它没有实现),或者在许多常见 IDisposal 实现模式中的任何一个的主处置例程开始之前插入处置逻辑,或者使用属性来指定处置的东西应该以特定名称进入例程。 一种通过默认行为(调用默认处置方法)或自定义行为(调用具有特定名称的方法)来确定性处置其构造函数和/或字段初始值设定项引发异常的对象的方法。 对于 vb.net,一种自动生成的方法来清空所有 WithEvent 字段。

所有这些都可以在 vb.net 中很好地组合在一起,而在 C# 中则不太好,但是对它们的一流支持会改进这两种语言。

【讨论】:

【参考方案7】:

您可以使用 finalize() 方法在 .net 和 java 中执行某种形式的 RAII。在 GC 清理类之前调用​​ finalize() 重载,因此可用于清理绝对不应由类保留的任何资源(互斥锁、套接字、文件句柄等)。但它仍然不是确定性的。

使用 .NET,您可以使用 IDisposable 接口和 using 关键字确定性地执行其中一些操作,但这确实有限制(在确定性行为需要使用时使用构造,仍然没有确定性内存释放,不会在类中自动使用等)。

是的,我觉得 RAII 的想法可以引入 .NET 和其他托管语言,尽管确切的机制可能会无休止地争论。我能看到的唯一另一种选择是引入一个可以处理任意资源清理(不仅仅是内存)的 GC,但是当绝对必须确定性地释放所述资源时,您就会遇到问题。

【讨论】:

以上是关于为啥 .NET 中没有 RAII?的主要内容,如果未能解决你的问题,请参考以下文章

RAII&智能指针

为啥 .NET 中没有可序列化 XML 的字典?

何时不使用 RAII 进行资源管理 [关闭]

C++ RAII

为啥.NET 没有内存泄漏?

为啥 .NET 中没有 SortedList<T>? [关闭]