↓C#をオンラインで学習するにはUdemy
私のようにJava上がりのプログラマーが、初めてC#のusingステートメントを見ると
「何これ?」
という感じです。
ただ、実際にC#のusingの概念を知って使ってみると、とても便利に感じます。
これぞ、長年プログラムを実装してきて、どんなバグが起こるかを知りきっている人が、未然にバグを防ぐ仕組みを実装したかの印象を受けます。
usingを一言で説明すると、usingを使ってプログラムでリソースをオープンした場合、usingのスコープを抜けると自動的に確保したリソースを解放してくれます。
ここでリソースとは、ファイルへのアクセスとかデータベースのアクセスなど、プログラム外のリソースに対してアクセスする手段を確保する何らかのメモリのことです。
usingの仕事は、単純なのですが、プログラムのエラーケースを考えると、バグの発生をおさえてくれる、とても便利な機能と分かります。
usingがどれだけ便利か、ちょっと例を見てみましょう。
目次
FileStreamとStreamReaderでファイルをオープンして表示するサンプル
これは、FormにButtonとTextBoxとOpenFileDialogを貼っただけのとても簡単なプログラムです。
ボタンクリックに以下の機能を実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
private void button1_Click(object sender, EventArgs e) { if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK) { FileStream fs = new FileStream(openFileDialog1.FileName, FileMode.Open); StreamReader sr = new StreamReader(fs); string text = sr.ReadToEnd(); textBox1.Text = text; MessageBox.Show("File Read Completed"); } } |
単純にテキストファイルをオープンして、TextBoxに表示するプログラムです。
一見、正しく動作しそうです。
ただ、1つのファイルをオープンした後、再度、同じファイルをオープンすると以下のエラーが発生します。
型 'System.IO.IOException' の初回例外が mscorlib.dll で発生しました
追加情報: 別のプロセスで使用されているため、プロセスはファイル 'C:\Users\xxxx\Documents\aaa.txt' にアクセスできません。
この例外のハンドラーがある場合は、プログラムを安全に続行できます。
そこで、プログラムの最後に以下の行を追加しました。
1 2 |
sr.Close(); fs.Close(); |
これで、一応エラーを回避することが出来ます。
FileStreamとStreamReaderを明示的にcloseするサンプル
もう少し実用的な例を考えてみましょう。
実際に実用されるプログラムでは、様々な処理が入ってきますし、プログラムの処理の途中で例外が発生する事がよくあります。
そこで下記では、わざと例外を発生させて、実用的なプログラムを模してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
private void button1_Click(object sender, EventArgs e) { if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK) { FileStream fs = new FileStream(openFileDialog1.FileName, FileMode.Open); //わざと例外発生 throw new Exception("Exception"); StreamReader sr = new StreamReader(fs); string text = sr.ReadToEnd(); textBox1.Text = text; MessageBox.Show("File Read Completed"); sr.Close(); fs.Close(); } } |
型 'System.Exception' のハンドルされていない例外が UsingSample.exe で発生しました
追加情報: Exception
(上記のダイアログのExcepionはスペルミスです。マイクロソフトの人、気づかないかなぁ。)
Exceptionをハンドルしていないので、このようなエラーが発生してしまいます。
そこで、Exceptionをcatchしたプログラムに変更してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
private void button1_Click(object sender, EventArgs e) { if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK) { FileStream fs = null; StreamReader sr = null; try { fs = new FileStream(openFileDialog1.FileName, FileMode.Open); //わざと例外発生 throw new Exception("Exception"); sr = new StreamReader(fs); string text = sr.ReadToEnd(); textBox1.Text = text; MessageBox.Show("File Read Completed"); } catch { sr.Close(); fs.Close(); MessageBox.Show("File Read Failed"); } } } |
一見、プログラムは正しそうなので実行してみます。
以下のExceptionが発生しました。
要はsrはNULLのままなので、sr.Close()が実行できないわけです。
型 'System.NullReferenceException' のハンドルされていない例外が UsingSample.exe で発生しました
追加情報: オブジェクト参照がオブジェクト インスタンスに設定されていません。
このエラーの回避方法として、単純に考えると以下のようになります。
1 2 3 4 5 6 7 8 |
if (sr != null) { sr.Close(); } if (fs != null) { fs.Close(); } |
2個のリソースのnullチェックなら、問題ないかもしれませんが、これがもっと沢山のリソースを使った場合はどうでしょうか?
Exceptionをcatchしたところで、nullになる可能性があるリソースをすべてnullチェックしていくのは、賢くないし、バグを引き起こす可能性を生み出しますね。
そこで、真打ち、usingの登場です。
usingを使ってFileStreamとStreamReaderを確実にcloseするサンプル
以下が、usingを使ったサンプルプログラムです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
private void button1_Click(object sender, EventArgs e) { try { if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK) { using (FileStream fs = new FileStream(openFileDialog1.FileName, FileMode.Open)) { //わざと例外発生 throw new Exception("Exception"); using (StreamReader sr = new StreamReader(fs)) { string text = sr.ReadToEnd(); textBox1.Text = text; } } MessageBox.Show("File Read Completed"); } } catch { MessageBox.Show("File Read Failed"); } } |
当然ながら、 throw new Exception("Exception")をコメントアウトしても動作します。
このプログラムの注目すべき点は、リソースのcloseもdisposeもしないし、nullチェックもしなくてよい、っていうことです。
using()でインスタンス化されたオブジェクトは、usingに続く{}を抜けると必ず解放されます。
これはとても便利です。
usingステートメント実装方法
usingを使ってインスタンス化するオブジェクトは、IDiposableインターフェースを実装したものに限ります。
IDisposableインターフェースを実装すると、必ず、IDisposable.Dispose()メソッドがコールされるために、インスタンスが確実に解放されるわけです。
標準関数なら、問題ないので、usingをとにかく使いましょう。
usingは文字通り、使っているっていう意味なので、
using(xxx) {}
となっていれば、{} の中ではxxxを使っている。
{} を抜ければ、xxxを使っていない、って考えれば分かりやすいです。
C#をオンラインで学習するには
↓C#をオンラインで学習するにはUdemy
コメント
staticな関数内でusingを使ってファイルアクセスした場合、明示的にCloseやDisposeをしないと例外が発生しました。
これはどうしてですか?
Staticな関数でファイルアクセス関数を作っていました。
例)static void FileSave(string filePath,stirng text)
この関数内でusing・・・StreamWriterを使っていますが明示的にCloseなどリソース解放しないと稀に例外発生します。