为什么.NET中没有RAII?

时间:2008-10-06 09:25:17

标签: .net struct destructor raii value-type

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

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

我的随机搜索发现一个参数,如果值类型是boxed,它属于垃圾收集器的管辖范围,因此它的破坏变得不确定。 我认为这个论点不够强大,RAII的好处足以说明带有析构函数的值类型不能被装箱(或用作类成员)。

简而言之,我的问题是:是否有任何其他原因无法使用值类型来将RAII引入.NET? (或者你认为我关于RAII明显优势的争论有缺陷吗?)

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

例如,对于具有通用确定性可破坏值的类型,我可以使usingusing关键字变为冗余(可由库类实现):

lock

我忍不住用我曾经看过的apropos报价结尾,但目前找不到它的来源。

  

当我冷酷的死手超出范围时,你可以采取我的确定性破坏。 - 匿名

7 个答案:

答案 0 :(得分:16)

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

您可能希望http://blogs.msdn.com/hsutter/archive/2004/07/31/203137.aspx

答案 1 :(得分:14)

优秀的问题和一个让我很困扰的问题。似乎RAII的好处有很大不同。根据我使用.NET的经验,缺乏确定性(或至少是可靠的)资源收集是主要缺点之一。实际上,.NET迫使我多次使用整个架构来处理可能(但可能不需要)显式收集的非托管资源。当然,这是一个巨大的缺点,因为它使整体架构更加困难,并将客户的注意力从更加核心的方面引导出来。

答案 2 :(得分:14)

Brian Harry发表了关于理由here的好文章。

以下是摘录:

  

确定性终结和价值类型(结构)怎么样?

     

--------------我见过很多关于结构的问题   析构函数等。这是值得的   评论。有各种各样的   为什么某些语言没有的问题   拥有它们。

     

(1)作文 - 他们不给你   一般的确定性寿命   同一种成分的情况   上述原因。任何   包含一个的非确定性类   直到它才会调用析构函数   无论如何,GC最终确定了。

     

(2)复制构造函数 - 一个地方   它真的很棒的地方   堆栈分配的本地人。他们将是   范围到方法,所有将是   大。不幸的是,为了得到   这真的有用,你也必须这样做   添加复制构造函数并调用它们   每次复制实例。   这是最丑陋的一个   关于C ++的复杂事情。你最终   让代码在整个地方执行   你不指望它的地方。它   导致一堆语言问题。   一些语言设计师选择了   远离这个。

     

假设我们用。创建了结构   destructors但添加了一堆   限制他们的行为   面对这些问题是明智的   以上。限制将是   类似的东西:

     

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

     

(2)你只能传递它们   通过-REF

     

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

     

(4)你不能打包   他们。

     

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

     

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

     

这些东西会有什么用处?将   你实际创建一个文件或   可以的数据库连接类   只能用作局部变量?一世   不相信任何人真的会。   你要做的是创造一个   通用连接然后   为...创建一个自动破坏的包装器   用作范围局部变量。该   然后来电者会选择他们的   想用。请注意来电者做了一个   决定并不完全   封装在对象本身中。   鉴于你可以使用一些东西   喜欢上面提出的建议   几个部分。

在.NET中替换RAII就是使用模式,一旦你习惯了,它几乎也能正常工作。

答案 3 :(得分:1)

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

答案 4 :(得分:1)

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

答案 5 :(得分:1)

恕我直言,VB.net和C#需要的大事是:

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

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

答案 6 :(得分:-3)

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

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

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