为什么在.NET中没有RAII?

主要是C ++开发人员,Java和.NET中RAII(资源获取初始化)的缺失一直困扰着我。 清理的责任从类作者转移到它的消费者(通过try finally或.NET的using构造 )的事实似乎显着地低下。

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

我的随机searchfind了一个说法,如果一个值types被装箱,它属于垃圾收集器的pipe辖范围,因此其销毁变得不确定。 我觉得这个论点还不够强,RAII的好处足以说明一个具有析构函数的值types不能被装箱(或用作类成员)。

长话短说我的问题是 :有什么其他的原因价值types不能用于引入RAII到.NET? (或者你认为我关于RAII显而易见的优势的观点是有缺陷的?)

编辑:由于前四个答案已经错过了这个观点,我一定没有清楚地expression这个问题。 我知道 Finalize及其非确定性特征,我知道using构造,我觉得这两个选项比RAII差。 using是一个类的消费者必须记住的另一件事(有多less人忘记把一个StreamReader放在一个using块?)。 我的问题是关于语言devise的一个哲学问题,为什么它是这样,可以改进?

例如,对于generics确定性可破坏的值types,我可以使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(); } } 

我不禁终止于我曾经看到但目前不能find其起源的恰当的引语。

当我冷酷的手离开范围,你可以采取我的确定性的破坏。 – 匿名

一个更好的标题将是“为什么在C#/ VB中没有RAII”。 C ++ / CLI(被pipe理的C ++的stream产的演变)具有与C ++完全相同的意义上的RAII。 对于相同的终止模式,其余的CLI语言都使用它们(C ++ / CLI的托pipe对象中的析构函数是有效的终结器),但它就在那里。

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

很好的问题和一个困扰我很大。 看起来RAII的好处被认为是非常不同的。 在我使用.NET的经验中,缺乏确定性(或至less可靠)的资源收集是主要的缺点之一。 实际上,.NET迫使我多次使用整个体系结构来处理可能 (但可能不需要)明确收集的非托pipe资源。 当然,这是一个巨大的缺点,因为它会使整个架构变得更加困难,并将客户的注意力从更重要的方面引导出来。

布莱恩·哈里在这里有一个很好的理由。

这是一个摘录:

那么确定性定价和价值types(结构)呢?

————–我看过很多关于析构函数的结构的问题,这是值得评论的。 有些语言为什么没有它们有很多问题。

(1)组成 – 在上述相同的组成原因下,它们在一般情况下不会给你确定性的生命期。 任何非确定性的类包含一个不会调用析构函数,直到GC最终确定。

(2)复制构造函数 – 真正好的一个地方是在栈分配的本地。 他们将被限定在方法中,一切都会很好。 不幸的是,为了得到这个真正的工作,你还必须添加复制构造函数,并在每次复制实例时调用它们。 这是关于C ++的最丑陋和最复杂的事情之一。 你最终得到的代码遍布你不期望的地方。 它会导致一堆语言问题。 有些语言devise者select远离这一点。

假设我们使用析构函数创build了结构,但添加了一系列限制,以使其行为在上述问题中变得明智。 限制将会是这样的:

(1)你只能将它们声明为局部variables。

(2)你只能通过他们的参考

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

(4)你不能装箱。

(5)通过reflection(迟绑定)使用它们的问题,因为通常涉及拳击。

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

这些东西会有什么用? 你真的会创build一个文件或数据库连接类,只能用作本地variables? 我不相信有人会。 你会做什么,而是创build一个通用连接,然后创build一个自动析构包装用作范围局部variables。 来电者然后将select他们想要使用的。 请注意,调用者作出了决定,并没有完全封装在对象本身。 鉴于你可以使用类似于几节中提出的build议。

.NET中RAII的替代是使用模式,一旦你习惯了,它的function就差不多了。

最接近你的是stackalloc操作符。

有一些类似的线程,如果你search它们,但基本上它归结为,如果你想在.NET上RAII只是实现一个IDisposabletypes,并使用“使用”的声明,以确定性的处置。 这样,许多相同的名字就可以用稍微更罗嗦的方式来实现和使用。

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

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

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

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

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

是的,我觉得在.NET和其他托pipe语言中可以引入RAII思想,尽pipe确切的机制可能会无休止地争论不休。 我能看到的唯一另外一种select是引入一个可以处理任意资源清理的GC(不仅仅是内存),但是当所有的资源必须被确定性地释放时,就会出现问题。