C#の「try-catch文」について解説します。「try-catch文」は例外処理に使うコードで、使いこなせるようになると、万が一エラーが発生してもプログラムを実行できます。「try-catch文」の基本的な使い方やテクニックを、サンプルコード付きでわかりやすく解説します。
C#の「try-catch文」は、プログラムのソースコードに記載された処理をtry(試行)して、エラー発生時にthrow(投球)された「例外」をcatch(捕捉)するための機能です。try-catch文は「例外処理」つまり「エラー処理」を行うために使います。
C#でtry-catch文を使ってエラー処理を行うことは、「例外処理」や「例外制御」と呼ばれます。この例外処理を正しく行うことで、たとえソースコードにエラーやバグがあったときでも、プログラムを問題なく実行できて便利です。つまりC#のtry-catch文は、安全なプログラムを作るために重要な機能ということです。
しかし、C#のtry-catch文は知っておくべきことが多いので、なかなか使いづらいこともあるでしょう。そこで本記事では、C#のtry-catch文の概要や機能、具体的な使い方と応用テクニックを解説します。
目次
C#の「try-catch文」は、プログラムの実行時に発生するエラーを適切に処理するための機能です。C#では、プログラムの実行中に何らかのエラーが発生すると、「例外(exception)」が発生します。
プログラムをまずtry(試行)してみて、例外が発生したときにcatch(捕捉)するというのが、今回ご紹介するtry-catch文の機能です。まず以下のサンプルコードで、try-catch文の全体像をチェックしてみましょう。
//サンプルプログラム |
//実行結果 |
上記のサンプルプログラムでは、「str変数」のインスタンスが生成されていないため、中身がヌルポインタになっています。これにアクセスしようとしたため、「NullReferenceException」という例外が発生しました。この「例外オブジェクト」を捕捉し、メッセージを表示するというのが、try-catch文で行っている処理内容です。
重要なポイントは、エラーが発生したものの、プログラム自体は最後まで実行されて「プログラムを終了します!」というメッセージが表示されていること。つまり、try-catch文による例外処理を行うと、エラー発生時にはその対応を行い、その後もプログラムの実行を継続できます。
try-catch文を使うと、前述したようにエラー時に発生する「例外」をcatch(捕捉)できます。それでは、例外をcatchしなければどうなるのでしょうか。以下のサンプルコードで実際に確認してみましょう。
//サンプルプログラム |
//実行結果 |
先ほどとほとんど同じソースコードですが、例外を捕捉せず放置しました。実は、例外を放置してもプログラム自体は動きます。しかしその結果、プログラムが異常終了して、最後の「プログラムを終了します!」というメッセージは表示されません。
なお、上記の画像で表示されている「Unhandled exception. System.NullReferenceException」というメッセージは、「ハンドルされていない例外”NullReferenceException”が発生しました」という意味です。
ハンドルされていない例外とは、放置された例外を意味します。今回発生した例外は「NullReferenceException」というもので、これはヌルポインタにアクセスしようとしたときに発生します。これを放置したため、「.NET」のシステムがプログラムを強制終了しました。
詳細は後述しますが、C#の例外処理はプログラムの異常終了防止やエラー原因特定に役立つ、非常に重要かつ便利な機能です。先ほど解説したように、C#の例外は放置してもプログラムは動きます。しかし、プログラムが異常終了してしまうことは大きな問題です。
業務システムやアプリなどの場合は、絶対に例外を放置してはいけません。業務用システムは事業継続のために欠かせないものであり、常時正常に稼働することが求められます。例外を放置してプログラムが異常終了すると、企業や顧客に多大な損害を与え、場合によっては生命が危険にさらされることもあるのです。
try-catch文で例外処理を行うことにより、仮にエラーが発生した場合でも代替の処理、たとえばエラーの発生個所をスキップするなどの処理が可能となります。結果的にプログラムの異常終了を回避でき、安全なプログラムが作れるようになります。
C#のtry-catch文を使いこなすために、まず下記7つの知識・基本テクニックを身につけておきましょう。
C#のtry-catch文の基本的な書き方・構文は以下のとおりです。先ほどのサンプルコードと合わせて、確認しておきましょう。
C#のtry-catch文による例外処理で重要なポイントは、「例外が発生する可能性がある処理」をtry節(tryブロック)で囲むことです。これにより、エラー発生時に投げられた例外を捕捉する準備が整います。なお、tryブロックで囲まなかった部分で例外が発生すると、プログラムが異常終了するので注意してください。
次のcatch節(catchブロック)では、捕捉する「例外オブジェクト」の種類を指定します。詳細は後述しますが、例外発生時に投げられる例外オブジェクトの実体は、「Exceptionクラス」を継承したクラスです。catch節では、間違った型の例外オブジェクトを指定すると、例外を捕捉できずプログラムが強制終了します。
try-catch文による例外処理の基本的な書き方を、以下のサンプルコードで確認しましょう。
//サンプルプログラム |
//実行結果 |
上記のサンプルプログラムは、割り算を行うという単純なものです。しかしゼロ除算を行っているため、「ArithmeticException」例外が発生してしまいます。try節の内部で割り算を行い、catch節でArithmeticExceptionの例外オブジェクトを捕捉し、メッセージを表示するという流れです。例外が発生した場合は、とりあえず戻り値として「0」を返します。
今回のプログラムでは、例外処理を正しく行っているため、ゼロ除算を行ったとしてもプログラムは異常終了せず、戻り値の表示も行えています。このことからも、C#のtry-catch文を使えば、安全なプログラムが書けることがわかります。
例外処理を行うと、tryブロックの処理の途中に例外が発生したとき、すぐにcatchブロックに飛びます。そのため、tryブロック内の処理は最後まで行われません。これは先ほどご紹介したサンプルプログラムのとおりです。
一方、try節の処理が問題なく完了すれば、catch節内の処理が行われることはありません。しかし、例外の発生の有無に関わらず必ず行いたい処理はあるでしょう。たとえば、オブジェクトの削除やリソースの解放処理などです。
そんなときに便利なのが「finally節」です。finallyブロック内に記載された処理は、例外が発生しても発生しなくても必ず実行されます。finally節を含むtry-catch-finally文の構文は、以下のとおりです。
finally節はたとえばファイル操作を行ったあと、ファイルをクローズするためによく使われます。ファイルはガベージコレクションが働かない「アンマネージドリソース」なので、プログラマーが自分でリソースの解放処理を行わないといけません。以下のサンプルコードで、finallyブロックの書き方を確認しましょう。
//サンプルプログラム |
//実行結果 |
ファイルが読み込めない場合は以下のように例外が表示されます。 |
上記のサンプルコードでは、Cドライブ直下に作成した「Testフォルダ」にある「Test」テキストファイルを読み込み、その中身を表示しているのです。その場所にファイルがない場合は「IOException」が発生するため、例外メッセージが表示されます。
finallyブロック内部では、「streamReader変数」のnullチェックを行い、リソースが生成されている場合は「Closeメソッド」で解放処理を行っていることがポイント。finally節を書かなければ、例外が発生したときにリソースの解放処理が行われないので、次に同じファイルにアクセスしたときに開けなくなってしまいます。
アンマネージドリソースを使うときは、「usingステートメント」が便利です。usingステートメントは、アンマネージドリソースを自動的に解放してくれる機能。先ほどのプログラムで「Usingステートメント」を使えば、以下のようにfinally節は不要となります。
//サンプルプログラム |
Usingステートメントは、変数のインスタンス生成文の先頭に「using」を付けるだけでOK。こうすると、変数が破棄されるときにリソースの破棄も同時に行われます。ただし、UsingステートメントはFileStreamやStreamRederなどのように、「IDisposableインターフェイス」を実装したクラスのみで使える機能です。
C#の例外処理では、以下の構文のようにcatchブロックをつなげれば、try節の処理が複数の例外が発生する可能性がある場合でも、状況に応じて適切な例外をcatchできるようになります。
ただし、一度に投げられる例外はひとつだけなので、catchできる例外はひとつです。つまり、実際に実行されるcatchブロックはひとつのみで、投げられる例外が変われば結果も変わります。詳細を以下のサンプルコードで確認しましょう。
//サンプルプログラム |
//実行結果 |
IOExceptionが発生した場合は以下のとおり。 Exceptionが発生した場合は以下のとおり。 |
上記のサンプルプログラムでは、3種類の例外をcatchする処理を並べています。なお、「FileNotFoundException」「IOException」「Exception」の3つの例外は、いずれも継承関係にあります。そのためFileNotFoundException例外は、IOExceptionとExceptionのどちらでも捕捉できるのですが、この場合は一番上に書いてあるFileNotFoundExceptionのcatchブロックが実行されます。
まったく関係がない例外オブジェクトをcatch節で指定しても、その部分の処理が実行されることはありません。
C#のcatch節では、捕捉したい例外オブジェクトの型名を、正確に指定する必要があります。しかし、捕捉すべき例外の型が分からなければ、単に「Exception」を指定するだけでもOKです。詳細を以下のサンプルコードで確認しましょう。
//サンプルプログラム |
|
//実行結果 |
|
先ほどのサンプルコードでは、「FileNotFoundException」や「IOException」の例外オブジェクトもcatchしていました。一方で、これらの例外オブジェクトはすべて「Exception」を継承しているため、catchブロックでは単にExceptionを指定するだけでも、例外を捕捉できるのです。
ただしExceptionを指定した場合は、スーパークラスにあるメソッドは使えません。たとえば、FileNotFoundExceptionには「FileNameプロパティ」や「FusionLogプロパティ」がありますが、Exceptionを指定した場合はこれらの機能は使えないので注意してください。
C#の例外処理では、自分で例外をthrowする(投げる)こともできます。呼び出し元のメソッドで改めて例外処理を行いたい場合や、自作のメソッドで例外を発生させたいときなどに便利です。自分で例外をthrowする構文は以下のとおりです。
例外オブジェクトはクラスなので、throwするときはnew演算子でコンストラクタを呼び出し、インスタンスを生成する必要があります。なお、引数のメッセージを指定しなかった場合は、デフォルト設定のメッセージが表示されます。詳細は以下のサンプルコードのとおりです。
//サンプルプログラム |
|
//実行結果 |
|
また、catchブロックで例外オブジェクトを指定したものの、「特定の条件のとき」だけ例外を捕捉したいときもあるでしょう。そのときは「when」で条件を付けることも可能で、構文は以下のとおりです。
上記の機能を「例外フィルター」と呼びます。たとえば条件文を「変数.Message.Equals(“メッセージ”)」と記載すると、「メッセージ」に合致する例外が投げられた場合のみ、catch節で例外を捕捉します。具体的な例は、以下のサンプルコードのとおりです。
//サンプルプログラム |
|
//実行結果 |
例外が捕捉された場合は以下のとおり。
条件に合致しない例外が発生した場合は以下のとおり。 |
上記のサンプルプロブラムでは、catchする例外オブジェクトとして「Exceptionクラス」を指定しつつも、特定のメッセージと該当するものだけ捕捉する仕様になっています。「0」以外を入力した場合は、例外の条件が合致しなくなるのでcatchされず、「ハンドルされていない例外が発生しました」という意味のメッセージが表示されます。
例外はさまざまな場所からthrowすることができる
C#の例外はメソッド以外にも、ラムダ式・NULL合体演算子・三項演算子など、さまざまな場所から例外をthrowできます。複数のパターンを以下のサンプルコードで確認しましょう。
//サンプルプログラム |
|
//実行結果 |
|
ちなみに「NULL合体演算子」とは、ある変数がNULLかNULL以外かで、処理を分岐させることができる機能です。「変数1 = 変数2 ?? 変数3」という構文で、変数2がnullでなければ変数2を、変数2がnullなら変数3を、変数1に代入できます。
上記のプログラムでは、「string str = obj as string ?? throw new Exception(“ヌルポインタです!”);」と記載していますが、これはobjをstring型に変換し、それがNULLでなければキャスト後の値をstrに代入し、NULLであれば例外を発生させるという意味です。ちなみに、上記以外の場所でthrowを書くとコンパイルエラーになります。
C#には膨大な数の例外オブジェクトが存在しますが、「よく出てくるもの」はある程度限られています。それを覚えておけば、「ソースコードのどこに問題があるか」「なぜ例外が発生したか」が分かりやすくなります。C#でよく使う基本的な例外オブジェクトと、例外の意味については以下のとおりです。
名前空間 | 例外オブジェクト | 概要 |
---|---|---|
System | ArgumentException | メソッドの引数が不正。 「ArgumentNullException」や「ArgumentOutOfRangeException」以外のエラー発生時に使用される |
ArgumentNullException | 引数がヌルポインタの場合 | |
ArgumentOutOfRangeException | メソッドの引数として、許容範囲外の値が渡された | |
ArithmeticException | 算術演算によるエラーが発生。 「OverflowException」「DivideByZeroException」「NotFiniteNumberException」以外の算術演算エラーが発生したときに使用される |
|
OverflowException | 算術演算やキャストでオーバーフローが発生した | |
DivideByZeroException | ゼロ除算を行ったときに発生する | |
NotFiniteNumberException | 浮動小数点の数値が無限大になった | |
FormatException | 引数の書式が仕様に合致していない、もしくは複合書式指定文字列が整形式でない | |
IndexOutOfRangeException | 配列やコレクションのインデックスに範囲外の値を設定した(いわゆる範囲外アクセス) | |
InvalidCastException | 無効なキャストが行われた | |
InvalidOperationException | 引数以外の原因で何らかのエラーが起きた | |
ObjectDisposedException | すでにDisposeされているオブジェクトで操作された | |
NotImplementedException | メソッドが未実装の場合に使用される。したがって、そのメソッドは使用してはならない | |
NotSupportedException | 呼び出されたメソッドの処理内容がサポートされていない、もしくは必要な機能を備えていない。したがって、そのメソッドは使用してはならない | |
NullReferenceException | nullオブジェクトにアクセスしようとした(Javaの「NullPointerException」と同義) | |
PlatformNotSupportException | 特定のプラットフォームにおいて機能が実行されていない | |
TimeoutException | 指定したタイムアウト時間が経過してもなお、処理が実行されない場合 | |
System.IO | DirectoryNotFoundException | 指定したディレクトリが存在しない |
FileNotFoundException | 指定したファイルが存在しない | |
EndOfStreamException | ファイル終端に到達してもなお、ファイルを読み込もうとしている | |
System.Collections.Generics | KeyNotFoundException | コレクションに該当するキーが存在しない |
上記の内容をすべて覚えておく必要はありません。一方で大まかにでも内容を理解しておくと、いざ「ハンドルされない例外」エラーが出たときも、すぐに対応策がわかるようになります。
以上の例外の中でも特に目にする機会が多いのが、「NullReferenceException」です。これはヌルポインタ、つまり「インスタンスが空のオブジェクト」へのアクセスで生じるもので、以下のように何気ないミスがきっかけで発生します。
//サンプルプログラム |
|
//実行結果 |
リストにある名前を検索した場合は以下のとおりです。
リストに無い名前を検索した場合は、以下のように「NullReferenceException」が発生します。 |
上記のサンプルコードの問題点は、「検索した名前がリストにない」ことを想定していないことです。「Array.Findメソッド」の仕様上、検索して該当要素がなかった場合は「Nullオブジェクト」が返ってきます。ユーザーがリストに無い名前を入力したとき、WriteLineメソッドでNullオブジェクトにアクセスするという想定外の事態が起きてしまうのです。なお、NullReferenceExceptionを防ぐ方法は主に以下3つあります。
以下のサンプルコードで、それぞれの手法によるNullReferenceException回避法を確認しましょう。
//サンプルプログラム |
|
//実行結果 |
|
どの方法が良いかはケースバイケースです。Unityなどのゲームエンジンを使う場合は、NullReferenceExceptionが頻繁に登場します。たとえば、すでに破棄したキャラクターオブジェクトやアイテムなど、存在しないはずのオブジェクトのインスタンスにアクセスしてしまうなどです。
C#で適切な例外処理を行うためには、try-catch文をうまく使いこなすことが大切です。そのために下記4つの注意点を意識しましょう。
「例外の握りつぶし」は極めて危険なので、絶対に行わないようにしてください。「例外の握りつぶし」とは、例外をキャッチしておきながら、cathcブロック内部で何も行わないことです。たとえば下記のサンプルコードは、例外の握りつぶしの典型例です。
以下のように「WriteLine」だけ使うのも、本来は良くありません。本記事に掲載したサンプルプログラムも、簡潔化のためにこのようになっていますが、本来であれば適切なエラー処理を行うべきです。
WriteLineメソッドでメッセージを表示すれば、「例外が発生していること」自体は分かるのでまだOKです。しかし、例外の握りつぶしは以下のサンプルコードのように、例外が発生したことさえ分からないため、「例外をcatchしないよりも悪い結果」をもたらします。
//サンプルプログラム |
|
//実行結果 |
|
上記のサンプルコードには重大な問題点があります。たとえば「A:\Test\Test.txt」など存在しないディレクトリを指定しても、何のエラーや例外も表示されずプログラムが終了するのです。そればかりか「無事に完了しました!」などというメッセージも表示されます。
そのため、ユーザーは「正確に作業できたもの」と理解して、何の疑いもなく作業を進めていきます。しかし、実際にはデータは保存されていないので、あるとき致命的なトラブルが発生してしまうかもしれません。
このプログラムは、本来なら「DirectoryNotFoundException」「FileNotFoundException」「UnauthorizedAccessException」などの例外が発生します。しかし、今回のサンプルコードでは肝心の例外を握りつぶしているため、重大なエラーが生じたことに気付きにくくなってしまいます。
なのでこの場合は、try-catch文を使用しないほうがよいでしょう。
C#で例外処理を行うときは、tryブロックの中身は簡潔なほうが好ましいです。下記のサンプルコードのように、「例外が発生する可能性がある処理」と「発生しない処理」を混在させると、処理の流れが追いづらくなるので注意してください。
//サンプルプログラム |
|
//実行結果 |
|
上記のサンプルコードには、大きな問題があるわけではありませんが、try節の中身が多すぎます。そのため、「どの部分で例外が発生する可能性があるか」「どんなエラーを想定しているか」など、プログラマーの意図や処理の流れが把握しにくくなります。
本来であれば、tryブロックの中身は「File.AppendAllTextメソッド」の部分だけで充分です。tryブロックの中では、例外を出す処理だけ書くほうがわかりやすいので、例外を出さない部分は外に出すべきです。
C#の例外処理(例外制御)では、catchブロック(catch節)で例外オブジェクトを受け取ります。ところが、前述したようにC#の例外オブジェクトには、さまざまな「型」があります。以下のサンプルコードのように、誤った例外オブジェクトを指定するとcatch節がうまく機能しません。
//サンプルプログラム |
|
//実行結果 |
|
上記のサンプルコードでは、ゼロ除算を行っています。try-catch文を使用していますが、catch節で「NullReferenceException」が指定されているため、例外を正しくcatchできません。本来であれば、「DivideByZeroException」や「ArithmeticException」を指定すべきです。
なお前述したように、「どの例外オブジェクトを指定すべきか分からない」場合は、「Exception」を指定しても問題ありません。その場合はすべての例外を捕捉可能となります。
C#には「検査例外」がないため、try-catch文を使わなくてもプログラムは書けるでしょう。一方で、検査例外は「catchしなければコンパイルエラーになる例外」を指します。C#の例外はすべて、必ずしもcatchしなくてもよい「非検査例外」です。この点はJavaとは大きく異なる部分なので、混同しないよう注意してください。
C#のtry-catch文や例外処理において、知っておくとより便利な「応用テクニック」を3つご紹介します。
例外オブジェクトは「Exception」のサブクラスなので、「Exception」クラスを継承すれば独自の例外オブジェクトが作れます。実際に以下のサンプルコードで、オリジナルの例外オブジェクトを作ってみましょう。
//サンプルプログラム |
|
//実行結果 |
|
C#では、ひとつのcatchブロックで複数の例外オブジェクトをcatchできます。これを「マルチキャッチ」と呼び、前述した「when」を使って以下の構文で実装します。
たとえば、「DirectoryNotFoundException」と「FileNotFoundException」で同じ例外処理をしたい場合は、catch節に両者のスーパークラスである「IOException」を指定し、when節に2つの例外オブジェクトを指定すれば大丈夫です。マルチキャッチは、ひとつの処理が複数の例外を投げる可能性があるときに、catchブロックを複数書かなくて良いので便利です。
//サンプルプログラム |
|
//実行結果 |
|
上記のサンプルコードでは、IOExceptionと関連がある「DirectoryNotFoundException」と「FileNotFoundException」に絞り込んで、catch節で受け取る例外オブジェクトとして指定しています。ただし、when節でまったく関係ない例外オブジェクトを指定しても、catchできないので気を付けましょう。
C#で例外メッセージを表示するとき、「StackTraceプロパティ」を使うと「スタックトレース」を表示できます。スタックトレースは「メソッドの呼び出し履歴」のことで、例外を発生させたメソッドがそのように呼び出されたかが分かる機能です。
スタックトレースを見れば、ソースコードや処理内容のどこに問題があるか可視化できるため、バグやエラーの改善に役立ちます。StackTraceプロパティの使い方を、以下のサンプルコードで確認しておきましょう。
//サンプルプログラム |
|
//実行結果 |
|
上記のサンプルプログラムでは、ユーザーに文字の入力を求め、それが「空入力」であったときに例外を発生させるようにしています。メッセージの表示部分では、StackTraceプロパティを活用し、詳細な情報を表示させていることがポイントです。
C#の「try-catch文」は、使わなくてもプログラムは書けます。しかし、プログラムが異常終了するのを防ぎ、安全に実行できるソースコードを書くためには、適切な例外処理・例外制御が必要です。とくに業務システムやアプリでは、try-catch文の知識は必要不可欠といっても過言ではありません。
C#try-catch文では、さまざまな型の例外オブジェクトを扱うため、なかなか慣れないことも多いはず。しかし、すべての例外が「Exception」のサブクラスであることや、よく見かける例外オブジェクトは限られていることを理解すると、意外と簡単に使えるようになります。C#のtry-catchをうまく活用しながらプログラムを書きましょう。
2024.06.17
子供におすすめのプログラミングスクール10選!学習メリットや教室選びのコツも紹介
#プログラミングスクール
2022.01.06
【完全版】大学生におすすめのプログラミングスクール13選!選ぶコツも詳しく解説
#プログラミングスクール
2024.01.26
【未経験でも転職可】30代におすすめプログラミングスクール8選!
#プログラミングスクール
2024.01.26
初心者必見!独学のJava学習方法とおすすめ本、アプリを詳しく解説
#JAVA
2024.01.26
忙しい社会人におすすめプログラミングスクール15選!失敗しない選び方も詳しく解説
#プログラミングスクール
2022.01.06
【無料あり】大阪のおすすめプログラミングスクール14選!スクール選びのコツも紹介
#プログラミングスクール
2024.01.26
【目的別】東京のおすすめプログラミングスクール20選!スクール選びのコツも徹底解説
#プログラミングスクール
2024.01.26
【無料あり】福岡のおすすめプログラミングスクール13選!選び方も詳しく解説
#プログラミングスクール
2024.01.26
【徹底比較】名古屋のおすすめプログラミングスクール13選!選び方も詳しく解説
#プログラミングスクール
2024.01.26
【徹底比較】おすすめのプログラミングスクール18選!失敗しない選び方も徹底解説
#プログラミングスクール