主要是一名 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 堵塞?)。我的问题是关于语言设计的哲学问题,为什么它是这样的以及可以改进吗?

例如,使用通用的确定性可破坏值类型,我可以使 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();
        }
    }

我忍不住引用了一段我曾经看过但目前无法找到其来源的恰当引文。

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

有帮助吗?

解决方案

更好的标题是“为什么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”声明获得确定性处置。这样,许多相同的意识形态只能以稍微冗长的方式实现和使用。

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

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

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

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

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top