我正在制作一个接收 System.Windows.Form 控件并设置其光标的 RAII 类。在析构函数中,它将光标设置回原来的样子。
但这是一个坏主意吗?当此类的对象超出范围时,我可以安全地依赖析构函数将被调用吗?
这是一个非常非常糟糕的主意。
当变量超出范围时,不会调用终结器。它们在对象被垃圾收集之前的某个时间点被调用,这可能是很长时间之后。
相反,您想实现IDisposable
,然后调用者可以使用:
using (YourClass yc = new YourClass())
{
// Use yc in here
}
那会Dispose
自动调用。
终结器在 C# 中很少需要 - 只有当您直接拥有非托管资源(例如 Windows 句柄)时才需要它们。否则,您通常会有一些托管包装类(例如FileStream
),如果需要,它会有一个终结器。
请注意,只有在需要清理资源时才需要这些——.NET 中的大多数类都没有实现IDisposable
.
编辑:只是为了回应关于嵌套的评论,我同意它可能有点难看,但根据我的经验,你不应该经常需要using
语句。如果你有两个直接相邻的,你也可以像这样垂直嵌套使用:
using (TextReader reader = File.OpenText("file1.txt"))
using (TextWriter writer = File.CreateText("file2.txt"))
{
...
}
你知道,很多聪明人说“如果你想在 C# 中实现 RAII,就使用 IDisposable”,我就是不买。我知道我在这里是少数,但是当我看到“using(blah) { foo(blah); }”时,我会自动认为“blah 包含一个非托管资源,一旦 foo 完成就需要积极清理(或抛出),以便其他人可以使用该资源”。我不认为“blah 没有任何有趣的内容,但需要发生一些语义上重要的突变,我们将用字符 '}' 来表示语义上重要的操作” - 必须弹出一些突变,如某些堆栈或必须重置某些标志或其他任何东西。
我说如果你有一个语义上重要的操作必须在某事完成时完成,我们有一个词,这个词是“终于”。如果操作很重要,那么它应该表示为您可以在此处看到并放置断点的语句,而不是右花括号的无形副作用。
因此,让我们考虑一下您的特定操作。你想代表:
var oldPosition = form.CursorPosition;
form.CursorPosition = newPosition;
blah;
blah;
blah;
form.CursorPosition = oldPosition;
该代码非常清楚。所有的副作用都在那里,想要了解您的代码在做什么的维护程序员可以看到。
现在你有了一个决策点。如果blah抛出怎么办?如果 blah 抛出,那么会发生意想不到的事情。您现在不知道“形式”处于什么状态;可能是“表单”中的代码抛出。它可能已经经历了一些突变,现在处于某种完全疯狂的状态。
鉴于这种情况,你想做什么?
1)把问题推给别人。没做什么。希望调用堆栈上的其他人知道如何处理这种情况。表格已处于不良状态的原因;它的光标不在正确的位置这一事实是它最不担心的。已经很脆弱的东西别戳了,报过一次异常。
2) 在 finally 块中重置光标,然后将问题报告给其他人。希望 - 没有任何证据表明您的希望会实现 - 在您知道处于脆弱状态的表单上重置光标本身不会引发异常。因为,在那种情况下会发生什么?可能有人知道如何处理的原始异常丢失了。您已经破坏了有关问题的原始原因的信息,这些信息可能有用。你已经用其他一些关于光标移动失败的信息替换了它,这对任何人都没有用。
3) 编写处理 (2) 问题的复杂逻辑——捕获异常,然后尝试在单独的 try-catch-finally 块中重置光标位置,该块抑制新异常并重新抛出原始异常。这可能很难做到正确,但你可以做到。
坦率地说,我认为正确的答案几乎总是(1)。如果出现了可怕的问题,那么您就无法安全地推断对脆弱状态的进一步突变是合法的。如果您不知道如何处理异常,请放弃。
(2) 是带有 using 块的 RAII 为您提供的解决方案。同样,我们首先使用块的原因是在它们不再可用时积极清理重要资源。无论是否发生异常,这些资源都需要快速清理,这就是为什么“使用”块具有“最终”语义的原因。但是“finally”语义不一定适用于不是资源清理操作的操作;正如我们所见,程序语义加载的 finally 块隐含地假设进行清理总是安全的;但是我们处于异常处理情况的事实表明它可能不安全。
(3) 听起来工作量很大。
所以简而言之,我说不要再这么聪明了。你想改变光标,做一些工作,然后取消光标。所以写三行代码来做这件事,完成。不需要花哨的RAII;它只是增加了不必要的间接性,使程序更难阅读,并且在特殊情况下可能更脆弱,而不是更脆弱。
编辑:显然埃里克和我在这种用法上存在分歧。:o
你可以使用这样的东西:
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
}
如果您想在 C# 中执行类似 RAII 的操作,请使用 IDisposable 模式