using(SomeClass x = new SomeClass("c:/temp/test.txt"))
{
...
}
using ブロック内では、例外を通常どおりに処理するだけで問題ありません。しかし、 のコンストラクターがSomeClass
例外をスローできる場合はどうなるでしょうか?
using(SomeClass x = new SomeClass("c:/temp/test.txt"))
{
...
}
using ブロック内では、例外を通常どおりに処理するだけで問題ありません。しかし、 のコンストラクターがSomeClass
例外をスローできる場合はどうなるでしょうか?
あなたの使い方をtry catch feに入れます
try
{
using(SomeClass x = new SomeClass("c:/temp/test.txt"))
{
...
}
}
catch(Exception ex)
{
...
}
はい、これはコンストラクターが例外をスローするときに問題になります。できることは、using ブロックを try/catch ブロック内にラップすることだけです。そのようにしなければならない理由はここにあります。
using ブロックは単なるシンタックス シュガーであり、コンパイラは各 using ブロックを同等の try/finall ブロックに置き換えます。唯一の問題は、コンパイラがコンストラクターを try ブロック内にラップしないことです。コンパイル後のコードは、IL で次の変換を行います。
//Declare object x of type SomeClass.
SomeClass x;
//Instantiate the object by calling the constructor.
x = new SomeClass("c:/temp/test.txt");
try
{
//Do some work on x.
}
finally
{
if(x != null)
x.Dispose();
}
コードからわかるように、コンストラクターが例外をスローした場合、オブジェクト x はインスタンス化されず、処理されない場合、コントロールは例外発生のポイントからそれ以上移動しません。
昨夜、この件に関するブログ投稿をブログに投稿しました。
なぜ C# の設計者がオブジェクトの構築を try ブロック内にラップしなかったのか、私はそうすべきだと思っています。
C# がオブジェクト構築を using ブロックの代わりに生成された try ブロックにラップしない理由を見つけたと思います。
理由は簡単です。宣言とインスタンス化の両方を try ブロック内でラップするthe object would be out of scope for the proceeding finally block
と、コードはコンパイルされません。なぜなら、finally ブロックではオブジェクトがほとんど存在しないからです。構造を try ブロックでラップし、try ブロックの前に宣言を保持するだけの場合、その場合でも、 が見つかるためコンパイルされませんyou're trying to use an assigned variable
。
これを確認するために簡単なテスト プログラムを一緒に投げましたが、コンストラクターで例外がスローされたときに Dispose メソッドが呼び出されないようです。
class Program
{
static void Main(string[] args)
{
try
{
using (OtherClass inner = new OtherClass())
{
Console.WriteLine("Everything is fine");
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.Read();
}
}
class OtherClass : IDisposable
{
public OtherClass()
{
throw new Exception("Some Error!");
}
void IDisposable.Dispose()
{
Console.WriteLine("I've disposed my resources");
}
}
出力:
いくつかのエラー!
例外をスローしない場合..
出力:
すべて順調
リソースを処分しました
おそらくこれは、オブジェクトが作成されていないためであり、Dispose を呼び出すものは何もありません。
コンストラクターが、通常は Dispose による適切なクリーンアップを必要とするリソースを既に割り当てていて、その後例外が発生した場合にどうなるかはわかりません。
これは、適切に設計されたクラスでは問題になりません。全体的な質問を覚えておいてください:
public class HoldsResources : IDisposable
{
public HoldsResources()
{
// Maybe grab some resources here
throw new Exception("whoops");
}
}
using (HoldsResources hr = new HoldsResources())
{
}
HoldsResources
問題は、コンストラクターが例外をスローする前に割り当てられたリソースをどうするかということです。
答えは、これらのリソースについて何もすべきではないということです。それはあなたの仕事ではありません。が資源を保持することが決定されたとき、それはHoldsResources
それらを適切に処分する義務をもたらしました. これは、コンストラクターでの try/catch/finally ブロックを意味IDisposable
し、メソッドでそれらのリソースを破棄するための適切な実装を意味しますDispose
。
あなたの責任は、インスタンスを使い終わったときに、using
ブロックを使用して彼のメソッドを呼び出すことです。Dispose
他には何もありません。
ガベージ コレクションの対象外のリソースを ctor 内で把握した場合は、事態が悪化したときにそれらを確実に破棄する必要があります。
このサンプルは、何か問題が発生したときにリークを防止する ctor を示しています。ファクトリ メソッド内でディスポーザブルを割り当てる場合と同じルールが適用されます。
class Sample
{
IDisposable DisposableField;
...
public Sample()
{
var disposable = new SomeDisposableClass();
try
{
DoSomething(disposable);
DisposableField = disposable;
}
catch
{
// you have to dispose of it yourself, because
// the exception will prevent your method/ctor from returning to the caller.
disposable.Dispose();
throw;
}
}
}
編集: サンプルをファクトリから ctor に変更する必要がありました。(コメントから判断します。)
もちろん、その理由は次のとおりです。ファクトリまたは ctor を呼び出すと、その結果のみを破棄できます。通話が終了したら、これまでのところすべて問題ないと想定する必要があります。
俳優や工場に電話するとき、とにかく手に入れることができないものを処分するために逆精神分析を行う必要はありません. 例外がスローされた場合、例外を再スローする前に半分割り当てられたものをすべてクリアするのは、ファクトリ/ctor の責任です。(今回は十分に精巧だったといいのですが...)