System.Windows.Form コントロールを取り込んでカーソルを設定する RAII クラスを作成していました。そしてデストラクタでは、カーソルを元の状態に戻します。
しかし、これは悪い考えですか?このクラスのオブジェクトがスコープ外になったときにデストラクタが呼び出されることを安全に信頼できますか?
これは非常にまずい考えです。
変数がスコープ外になると、ファイナライザーは呼び出されません。それらは、オブジェクトがガベージ コレクションされる前のある時点で呼び出されます。これには、かなり時間がかかる可能性があります。
代わりに、 を実装したい場合IDisposable
、呼び出し元は以下を使用できます。
using (YourClass yc = new YourClass())
{
// Use yc in here
}
それはDispose
自動的に呼び出されます。
C# でファイナライザーが必要になることはほとんどありません。ファイナライザーが必要になるのは、アンマネージ リソース (Windows ハンドルなど)を直接所有している場合のみです。FileStream
それ以外の場合は、通常、必要に応じてファイナライザーを持つマネージ ラッパー クラス ( など) があります。
これは、クリーンアップするリソースがある場合にのみ必要であることに注意してください。.NET のほとんどのクラスは実装していませんIDisposable
。
編集: ネストに関するコメントに応答するためだけに、少し醜いかもしれないことに同意しますが、私の経験では、using
ステートメントを頻繁に必要とするべきではありません。直接隣接する2 つの using がある場合は、このように縦にネストすることもできます。
using (TextReader reader = File.OpenText("file1.txt"))
using (TextWriter writer = File.CreateText("file2.txt"))
{
...
}
ご存知のように、多くの賢明な人々が「C# で RAII を実装したい場合は IDisposable を使用してください」と言っていますが、私はそれを購入しません。ここでは私が少数派であることは知っていますが、「using(blah) { foo(blah); }」を見ると、「何とか foo が完了したらすぐに積極的にクリーンアップする必要がある管理されていないリソースが含まれている」と自動的に思います (またはスロー) 他の誰かがそのリソースを使用できるようにします。」「何とか何も興味深いものは含まれていませんが、発生する必要がある意味的に重要な変更があり、その意味的に重要な操作を文字「}」で表すつもりです」-スタックのようないくつかの変更をポップする必要があるか、いくつかのフラグをリセットする必要があります。
何かが完了したときに行わなければならない意味的に重要な操作がある場合、それを表す単語があり、その単語は「最終的に」です。操作が重要な場合は、右中括弧の目に見えない副作用ではなく、すぐそこに表示され、ブレークポイントを配置できるステートメントとして表す必要があります。
それでは、あなたの特定の操作について考えてみましょう。あなたが表現したい:
var oldPosition = form.CursorPosition;
form.CursorPosition = newPosition;
blah;
blah;
blah;
form.CursorPosition = oldPosition;
そのコードは完全に明確です。すべての副作用はすぐそこにあり、コードが何をしているかを理解したいメンテナンス プログラマーに表示されます。
これで決定点ができました。何とか投げたらどうなりますか?何とかスローすると、予期しないことが起こりました。「フォーム」がどのような状態にあるかはわかりません。スローしたのは「フォーム」のコードだった可能性があります。突然変異の途中だった可能性があり、現在は完全にクレイジーな状態になっています。
その状況を踏まえて、あなたはどうしたいですか?
1) 問題を他の人にパントします。何もしない。コール スタックの他の誰かがこの状況に対処する方法を知っていることを願っています。フォームがすでに悪い状態にある理由。カーソルが正しい場所にないという事実は、その心配の中で最も少ないものです。すでに非常に壊れやすいものを突っ込まないでください。一度例外が報告されています。
2) finally ブロックでカーソルをリセットし、問題を他の人に報告します。希望が実現されるという証拠はまったくありませんが、壊れやすい状態にあることがわかっているフォームでカーソルをリセットしても、それ自体が例外をスローしないことを願っています。だって、その場合どうなるの?おそらく誰かが処理方法を知っていた元の例外は失われます。問題の元の原因に関する情報、有用であった可能性のある情報を破棄しました。そして、カーソル移動の失敗に関する他の情報に置き換えましたが、これは誰の役にも立ちません。
3) (2) の問題を処理する複雑なロジックを記述します -- 例外をキャッチし、新しい例外を抑制して元の例外を再スローする別の try-catch-finally ブロックでカーソル位置をリセットしようとします。これを正しく行うのは難しい場合がありますが、実行できます。
率直に言って、私の考えでは、正解はほとんどの場合 (1) です。何か恐ろしいことがうまくいかなかった場合、脆弱な状態へのさらなる突然変異が合法であることを安全に推論することはできません. 例外の処理方法がわからない場合は、あきらめてください。
(2) は、using ブロックを使用した RAII が提供するソリューションです。繰り返しになりますが、そもそもブロックを使用する理由は、重要なリソースが使用できなくなったときに積極的にクリーンアップするためです。例外が発生するかどうかにかかわらず、これらのリソースは迅速にクリーンアップする必要があります。これが、「使用中」のブロックが「最終的に」セマンティクスを持つ理由です。しかし、「最後に」セマンティクスは、リソースのクリーンアップ操作ではない操作には必ずしも適切ではありません。これまで見てきたように、プログラム セマンティクスを積んだ finally ブロックは、クリーンアップを実行しても常に安全であるという仮定を暗示的に行います。しかし、例外処理の状況にあるという事実は、それが安全ではない可能性があることを示しています。
(3) 大変な作業のように思えます。
要するに、私はそんなに賢くなろうとするのをやめると言います. カーソルを変更し、いくつかの作業を行い、カーソルの変更を解除します。それで、それを行う 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 パターンを使用します。