为什么.NET 中没有 RAII?
-
05-07-2019 - |
题
主要是一名 C++ 开发人员,缺乏 RAII(资源获取即初始化) Java 和 .NET 中的问题一直困扰着我。事实上,清理的责任从类编写者转移到了其使用者(通过 try finally
或 .NET 的 using
构造) 似乎明显逊色。
我明白为什么在 Java 中不支持 RAII,因为所有对象都位于堆上,并且垃圾收集器本质上不支持确定性销毁,但在 .NET 中引入了值类型(struct
)我们有(看似)完美的 RAII 候选者。在堆栈上创建的值类型具有明确定义的范围,并且可以使用 C++ 析构函数语义。但是,CLR 不允许值类型具有析构函数。
我的随机搜索发现一个论点,如果值类型是 盒装的 它属于垃圾收集器的管辖范围,因此它的销毁变得不确定。我觉得这个论点不够有力,RAII 的好处足以说明带有析构函数的值类型不能被装箱(或用作类成员)。
长话短说,我的问题是: :为了将 RAII 引入 .NET,还有其他原因无法使用值类型吗?(或者你认为我关于 RAII 明显优势的论点有缺陷吗?)
编辑: 我一定没有清楚地表达这个问题,因为前四个答案都没有抓住重点。我 知道 关于 Finalize
及其非确定性特征,我知道 using
我觉得这两个选项都不如 RAII。 using
这是一个类的消费者必须记住的另一件事(有多少人忘记了 StreamReader
在一个 using
堵塞?)。我的问题是关于语言设计的哲学问题,为什么它是这样的以及可以改进吗?
例如,使用通用的确定性可破坏值类型,我可以使 using
和 lock
关键字冗余(可通过库类实现):
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();
}
}
我忍不住引用了一段我曾经看过但目前无法找到其来源的恰当引文。
当我冰冷、死亡的手超出范围时,你可以承受我的确定性破坏。--匿名
解决方案
更好的标题是“为什么C#/ VB中没有RAII”。 C ++ / CLI(Managed C ++的堕胎的演变)具有与C ++完全相同的RAII。对于其他CLI语言使用的相同终结模式,它只是语法糖(C ++ / CLI的托管对象中的析构函数是有效的终结器),但它就在那里。
您可能会喜欢 http://blogs.msdn.com /hsutter/archive/2004/07/31/203137.aspx
其他提示
优秀的问题和一个让我很困扰的问题。似乎RAII的好处有很大不同。根据我使用.NET的经验,缺乏确定性(或至少是可靠的)资源收集是主要缺点之一。实际上,.NET迫使我多次使用整个架构来处理可能(但可能不需要)显式收集的非托管资源。当然,这是一个巨大的缺点,因为它使整体架构更加困难,并将客户的注意力从更加核心的方面引导出来。
Brian Harry在此处上有一篇关于理由的帖子。
以下是摘录:
确定性终结和价值类型(结构)怎么样?
--------------我见过很多关于结构的问题 析构函数等。这是值得的 评论。有各种各样的 为什么某些语言没有的问题 拥有它们。
(1)作文 - 他们不给你 一般的确定性寿命 同一种成分的情况 上述原因。任何 包含一个的非确定性类 直到它才会调用析构函数 无论如何,GC最终确定了。
(2)复制构造函数 - 一个地方 它真的很棒的地方 堆栈分配的本地人。他们将是 范围到方法,所有将是 大。不幸的是,为了得到 这真的有用,你也必须这样做 添加复制构造函数并调用它们 每次复制实例。 这是最丑陋的一个 关于C ++的复杂事情。你最终 让代码在整个地方执行 你不指望它的地方。它 导致一堆语言问题。 一些语言设计师选择了 远离这个。
假设我们用。创建了结构 destructors但添加了一堆 限制他们的行为 面对这些问题是明智的 以上。限制将是 类似的东西:
(1)您只能将它们声明为本地 变量。
(2)你只能传递它们 通过-REF
(3)你不能分配它们 只能访问字段和调用 他们的方法。
(4)你不能打包 他们。
(5)使用它们的问题 反思(后期绑定)因为那个 通常涉及拳击。
也许更多, 但这是一个好的开始。
这些东西会有什么用处?将 你实际创建一个文件或 可以的数据库连接类 只能用作局部变量?一世 不相信任何人真的会。 你要做的是创造一个 通用连接然后 为...创建一个自动破坏的包装器 用作范围局部变量。该 然后来电者会选择他们的 想用。请注意来电者做了一个 决定并不完全 封装在对象本身中。 鉴于你可以使用一些东西 喜欢上面提出的建议 几个部分。
在.NET中替换RAII就是使用模式,一旦你习惯了,它几乎也能正常工作。
最接近的是stackalloc运算符非常有限。
如果你搜索它们有一些类似的线程,但基本上它归结为如果你想在.NET上使用RAII只需实现一个IDisposable类型并使用“using”声明获得确定性处置。这样,许多相同的意识形态只能以稍微冗长的方式实现和使用。
- a“使用”字段声明,这将导致编译器生成代码以处理由此标记的所有字段。默认行为应该是编译器使类实现IDisposable(如果没有),或者在主处理例程的开始之前为许多常见的IDisposal实现模式中的任何一个插入处理逻辑,或者使用属性指定处理的东西应该放在具有特定名称的例行程序中。
- 通过默认行为(调用默认处理方法)或自定义行为(调用具有特定名称的方法)确定性地处理构造函数和/或字段初始值设定项引发异常的对象的方法。
- 对于vb.net,一个自动生成的方法,用于清除所有WithEvent字段。
所有这些都可以在vb.net中很好地解决,而在C#中则不太好,但是对它们的一流支持会改善两种语言。
您可以使用 Finalize() 方法在 .net 和 java 中执行某种形式的 RAII。Finalize() 重载在 GC 清理类之前调用,因此可用于清理绝对不应该由类保留的任何资源(互斥体、套接字、文件句柄等)。但它仍然不是确定性的。
使用 .NET,您可以使用 IDisposable 接口和 using 关键字来确定性地执行某些操作,但这确实有局限性(在需要确定性行为时使用构造,仍然没有确定性的内存释放,不会在类中自动使用等)。
是的,我认为 RAII 思想有必要引入到 .NET 和其他托管语言中,尽管确切的机制可能会无休止地争论。我能看到的唯一的其他选择是引入一个 GC,它可以处理任意资源清理(不仅仅是内存),但是当所述资源绝对必须被确定性地释放时,您就会遇到问题。