RAII在C#中是否可以安全使用?和其他垃圾收集语言?

时间:2009-11-18 16:08:46

标签: c# raii

我正在创建一个RAII类,它接受System.Windows.Form控件,并设置其光标。在析构函数中,它将光标设置回原来的状态。

但这是个坏主意吗?当这个类的对象超出范围时,我可以安全地依赖析构函数吗?

4 个答案:

答案 0 :(得分:20)

这是一个非常非常糟糕的主意。

当变量超出范围时,终结器被调用。在对象被垃圾收集之前的某个时刻被调用,这可能需要很长时间。

相反,您希望实现IDisposable,然后调用者可以使用:

using (YourClass yc = new YourClass())
{
    // Use yc in here
}

这将自动调用Dispose

在C#中很少需要终结器 - 只有在直接拥有非托管资源(例如Windows句柄)时才需要它们。否则,您通常会有一些托管包装类(例如FileStream),如果需要,它将具有终结器。

请注意,当您需要清理资源时,只需要任何 - .NET 中的大多数类不会实现IDisposable。< / p>

编辑:只是为了回应有关嵌套的评论,我同意它可能有点难看,但根据我的经验,你不应该经常需要using语句非常。你可以像这样垂直嵌套使用,如果你有两个直接相邻的

using (TextReader reader = File.OpenText("file1.txt"))
using (TextWriter writer = File.CreateText("file2.txt"))
{
    ...
}

答案 1 :(得分:12)

你知道,很多聪明人说“如果你想在C#中实现RAII,就使用IDisposable”,而我只是不买它。我知道我在这里是少数,但是当我看到“使用(blah){foo(blah);}”时,我自动认为“blah包含一个非托管资源,需要在foo完成后立即进行清理(或投掷)以便其他人可以使用该资源“。我不认为“blah不包含任何有趣的内容,但是有一些语义上重要的突变需要发生,我们将通过字符'}'表示语义上重要的操作” - 一些突变就像必须弹出一些堆栈或者必须重置某些标志或其他任何东西。

我说如果你有一个必须在某些事情完成时完成的语义上重要的操作,我们就有了一个单词,那个单词就是“finally”。如果操作重要,那么它应该表示为一个语句,你可以在那里看到并设置断点,而不是右大括号的不可见副作用。

让我们考虑一下你的特定操作。你想代表:

var oldPosition = form.CursorPosition;
form.CursorPosition = newPosition;
blah;
blah;
blah;
form.CursorPosition = oldPosition;

该代码完全清晰。所有副作用都在那里,对于想要了解代码正在做什么的维护程序员来说是可见的。

现在你有一个决策点。怎么回事呢?如果等等,那么意外发生了。你现在不知道“形式”是什么状态;它可能是“形式”中的代码。它可能是一些突变的中途,现在处于一种完全疯狂的状态。

鉴于这种情况,你想做什么?

1)将问题提交给其他人。没做什么。希望调用堆栈中的其他人知道如何处理这种情况。表格已经处于不良状态的原因;光标不在正确位置的事实是它最不担心的事实。不要盯着已经如此脆弱的东西,它曾经被报道过一次异常。

2)将光标重置在finally块中,然后将问题报告给其他人。希望 - 没有任何证据证明你的希望将会实现 - 将光标重置在你知道处于脆弱状态的表单上本身不会引发异常。因为,那种情况会发生什么?可能有人知道如何处理的原始异常丢失了。您已经销毁了有关问题原因的信息,这可能是有用的信息。而且你已经用一些关于光标移动故障的其他信息取而代之,这对任何人都没用。

写出处理(2)问题的复杂逻辑 - 捕获异常,然后尝试在单独的try-catch-finally块中重置光标位置,该块会抑制新异常并重新抛出原始异常例外。这可能很难做到,但你可以做到。

坦率地说,我的信念是,正确的答案几乎总是(1)。如果出现了可怕的问题,那么你无法安全地推断脆弱国家的进一步突变是合法的。如果您不知道如何处理异常,那么放弃。

(2)是带有使用块的RAII为您提供的解决方案。同样,我们首先使用块的原因是在不再使用块时积极清理重要资源。 是否发生异常,需要快速清理这些资源,这就是“使用”块具有“最终”语义的原因。但“最终”语义不一定适用于非资源清理操作的操作;正如我们所看到的,程序语义 - 负载最终阻止隐含地假设清理总是安全的;但我们处于异常处理状态的事实表明它可能不安全。

(3)听起来很多工作。

所以简而言之,我说停止尝试这么聪明。你想改变光标,做一些工作,并取消突变光标。所以写完三行代码就完成了。没有华丽的裤子RAII是必要的;它只是增加了不必要的间接性,它使得阅读程序变得更加困难,并且在特殊情况下它可能更容易更多脆弱,而不是更脆弱。

答案 2 :(得分:6)

编辑:显然,Eric和我对这种用法存在分歧。 :○

您可以使用以下内容:

public sealed class CursorApplier : IDisposable
{
    private Control _control;
    private Cursor _previous;

    public CursorApplier(Control control, Cursor cursor)
    {
        _control = control;
        _previous = _control.Cursor;
        ApplyCursor(cursor);
    }

    public void Dispose()
    {
        ApplyCursor(_previous);
    }

    private void ApplyCursor(Cursor cursor)
    {
        if (_control.Disposing || _control.IsDisposed)
            return;

        if (_control.InvokeRequired)
            _control.Invoke(new MethodInvoker(() => _control.Cursor = cursor));
        else
            _control.Cursor = cursor;
    }
}

// then...

using (new CursorApplier(control, Cursors.WaitCursor))
{
    // some work here
}

答案 3 :(得分:1)

如果你想在C#中做类似RAII的事情,请使用IDisposable模式