为什么.NET 中没有 RAII?

发布于 2024-07-06 13:04:33 字数 1666 浏览 9 评论 0原文

作为主要的 C++ 开发人员,Java 和 .NET 中缺少 RAII(资源获取即初始化)总是困扰着我。 事实上,清理的责任从类编写者转移到了其使用者(通过 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();
        }
    }

我忍不住以 apropos 结尾我曾经见过但目前无法找到其来源的引文。

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

Being primarily a C++ developer the absence of RAII (Resource Acquisition Is Initialization) in Java and .NET has always bothered me. The fact that the onus of cleaning up is moved from the class writer to its consumer (by means of try finally or .NET's using construct) seems to be markedly inferior.

I see why in Java there is no support for RAII since all objects are located on the heap and the garbage collector inherently doesn't support deterministic destruction, but in .NET with the introduction of value-types (struct) we have the (seemingly) perfect candidate for RAII. A value type that's created on the stack has a well defined scope and C++ destructor semantics can be used. However the CLR does not permit a value-type to have a destructor.

My random searches found one argument that if a value-type is boxed it falls under the jurisdiction of the garbage collector and therefore its destruction becomes non-deterministic.
I feel that this argument isn't strong enough, the benefits of RAII are big enough to say that a value-type with a destructor cannot be boxed (or used as a class member).

To cut a long story short my question is: are there any other reasons value types can not be used in order to introduce RAII to .NET? (or do you think my argument about RAII's obvious advantages are flawed?)

Edit: I must have not phrased the question clearly since the first four answers have missed the point. I know about Finalize and its non-deterministic characteristics, I know about the using construct and I feel these two options are inferior to RAII. using is one more thing the consumer of a class must remember (how many people forgot to put a StreamReader in a using block?). My question is a philosophical one about the language design, why is it the way it is and can it be improved?

For instance with a generic deterministically destructible value-type I can make the using and lock keywords redundant (achievable by library classes):

    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();
        }
    }

I can't help but end with a apropos quotation which I once saw but can't currently find its origin.

You can take my deterministic destruction when my cold dead hand goes out of scope. --Anon

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

念三年u 2024-07-13 13:04:33

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

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

A better title would be "Why is there no RAII in C#/VB". C++/CLI (The evolution of the abortion that was Managed C++) has RAII in the exact same sense as C++. It's all just syntax sugar for the same finalisation pattern that the rest of the CLI languages use (Destructors in managed objects for C++/CLI are effectively finalisers), but it is there.

You might like http://blogs.msdn.com/hsutter/archive/2004/07/31/203137.aspx

黑白记忆 2024-07-13 13:04:33

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

Excellent question and one that has bothered me greatly. It appears that the benefits of RAII are perceived very differently. In my experience with .NET, the lack of deterministic (or at least reliable) resource collection is one of the major drawbacks. In fact, .NET has forced me several times to employ whole architectures to deal with unmanaged resources that might (but might not) require explicit collecting. Which, of course, is a huge drawback because it makes the overall architecture more difficult and directs the client's attention away from the more central aspects.

橘香 2024-07-13 13:04:33

Brian Harry 在此处发表了一篇关于其基本原理的精彩文章。

以下是摘录:

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

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

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

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

假设我们创建了结构体
析构函数但添加了一堆
限制他们的行为
面对问题要理智
多于。 限制是
像这样:

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

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

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

(4) 你不能装箱
他们。

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

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

这些东西有什么用呢? 会
您实际上创建了一个文件或
数据库连接类可以
只能用作局部变量? 我
不相信有人真的会。
相反,你要做的是创建一个
通用连接,然后
创建一个自动销毁的包装器
用作作用域局部变量。 这
然后呼叫者会选择他们想要的内容
想用。 请注意呼叫者发出了
决定并不完全是
封装在对象本身中。
鉴于你可以使用一些东西
就像出现的建议一样
几个部分。

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

Brian Harry has a nice post about the rationales here.

Here is an excerpt:

What about deterministic finalization and value types (structs)?

-------------- I've seen a lot of questions about structs having
destructors, etc. This is worth
comment. There are a variety of
issues for why some languages don't
have them.

(1) composition - They don't give you
deterministic lifetime in the general
case for the same kinds of composition
reasons described above. Any
non-deterministic class containing one
would not call the destructor until it
was finalized by the GC anyway.

(2) copy constructors - The one place
where it would really be nice is in
stack allocated locals. They would be
scoped to the method and all would be
great. Unfortunately, in order to get
this to really work, you also have to
add copy constructors and call them
every time an instance is copied.
This is one of the ugliest and most
complex things about C++. You end up
getting code executing all over the
place where you don't expect it. It
causes bunches of language problems.
Some language designers have chosen to
stay away from this.

Let's say we created structs with
destructors but added a bunch of
restrictions to make their behavior
sensible in the face of the issues
above. The restrictions would be
something like:

(1) You can only declare them as local
variables.

(2) You can only pass them
by-ref

(3) You can't assign them, you
can only access fields and call
methods on them.

(4) You can't box
them.

(5) Problems using them through
Reflection (late binding) because that
usually involves boxing.

maybe more,
but that's a good start.

What use would these things be? Would
you actually create a file or a
database connection class that can
ONLY be used as a local variable? I
don't believe anybody really would.
What you would do instead is create a
general purpose connection and then
create an auto destructed wrapper for
use as a scoped local variable. The
caller would then pick what they
wanted to use. Note the caller made a
decision and it is not entirely
encapsulated in the object itself.
Given that you could use something
like the suggestions coming up in a
couple of sections.

The replacement for RAII in .NET is the using-pattern, which works almost as well once you get used to it.

忆离笙 2024-07-13 13:04:33

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

The closest you get to that is the very limited stackalloc operator.

我不在是我 2024-07-13 13:04:33

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

There's some similar threads if you search for them but basicly what it boils down to is that if you want to RAII on .NET simply implement an IDisposable type and use the "using" statement to get deterministic Disposal. That way many of the same ideoms can be implemented and used in only a slightly more wordy manner.

祁梦 2024-07-13 13:04:33

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

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

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

IMHO, the big things that VB.net and C# need are:

  1. a "using" declaration for fields, which would cause the compiler to generate code to dispose of all fields thus tagged. The default behavior should be for the compiler to make a class implement IDisposable if it does not, or insert disposal logic before the start of the main disposal routine for any of a number of common IDisposal implementation patterns, or else use an attribute to specify that the disposal stuff should go in a routine with a particular name.
  2. a means of deterministically disposing of objects whose constructors and/or field initializers throw an exception, either by a default behavior (calling the default disposal method) or a custom behavior (calling a method with a particular name).
  3. For vb.net, an auto-generated method to null out all WithEvent fields.

All of those can be kludged pretty well in vb.net, and somewhat less well in C#, but first-class support for them would improve both languages.

巷子口的你 2024-07-13 13:04:33

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

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

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

You can do a form of RAII in .net and java using finalize() methods. A finalize() overload is called before the class is cleaned up by the GC and so can be used to clean up any resources that absolutely shouldn't be kept by the class (mutexes, sockets, file handles, etc). It still isn't deterministic though.

With .NET you can do some of this deterministically with the IDisposable interface and the using keyword, but this does have limitations (using construct when used required for deterministic behaviour, still no deterministic memory deallocation, not automatically used in classes, etc).

And yes, I feel there is a place for RAII ideas to be introduced into .NET and other managed languages, although the exact mechanism could be debated endlessly. The only other alternative I could see would be to introduce a GC that could handle arbitrary resource cleanup (not just memory) but then you have issues when said resources absolutely have to be released deterministically.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文