こんにちは、リバティエンジニア[?]のFUNAです。 現役エンジニアとしてアプリケーション開発やWeb制作、SEOやブログ運営をしています。
ここではプログラミングをする際に必須となってくる例外処理の正しいやり方や注意点を私が実際に躓いたことなども紹介しながら解説したいと思います。
C# 例外処理とは?
例外はユーザーが入力フォームに想定外の文字列を入力してきたときや、 プログラムに必要なファイルが開けなかったときなどに発生します。
例えば、文字列を整数に変換する時に簡単化するために、とりあえず正の整数のみを扱うとします。 想定外の文字列を考慮しない場合プログラムとしては以下のよう記述します。
// 文字→整数
static int CharToInt(char c)
{
return c - '0';
}
// 文字列→整数
static int StringToInt(string str)
{
int val = 0;
foreach(char c in str)
{
int i = CharToInt(c);
val = val * 10 + i;
}
return val;
}
このように実装された関数を実際に使う側(ユーザー)がもし数値以外の値の入ったものを入力した時にはもちろん正常動作はしません。
このような時に想定外の事が起こった場合でも考慮する必要があるのです。
その考慮をすることを「例外処理」といいます。
C# 例外とは?
まずはそもそも「例外」について説明します。
一言で言ってしまうと、
しっかりとしたプログラムを作成するためには、万が一例外が起こったときでもプログラムが異常な動作をしないように例外処理(exception handling)をしなければなりません。
ですがこのあたり流石C#ですね。
例外処理を行うための専用の構文が用意されており、 プログラマが例外処理を簡単に行えるようになっていますので紹介します。
C# 例外処理の構文
ご紹介する例外処理の構文は「try-catch-finally」を使ったやり方です。
try
{
// 例外が投げられる可能性のあるコード
}
catch("例外の種類")
{
// 例外処理コード
}
finally
{
// 例外発生の有無にかかわらず実行したいコード
// リソースの破棄などを行う
}
使い方としてはこのようになります。
まず例外が発生しそうなところを"try"で囲みます。
もしその中で例外が発生した場合は、処理を中断して"catch"の中に入り例外処理をします。
"finally"では例外が出た場合も出なかった場合も必ず実行される処理を記述します。
簡単ですよね。
ある程度自分の想定できないような例外が発生しそうだと思うところではこの構文を使って、例外処理をすることをおすすめします。
この例外処理の構文は重ねて使うこともできます。
その際にしっかりと例外の種類や例外時の処理をそれぞれわけて明確化してやらないと、実際に例外が発生した時にどこで起きてどんな例外なのかわからなくなるので、注意してくださいね!(私も以前はよくやらかしてしまっていました。。)
C# 例外の種類(標準の例外クラス)
.NET Frameowrkが標準で提供している例外クラスの中でも、よく使うものをいくつか例に挙げていきます。
クラス名 | throw される状況 |
---|---|
System 名前空間 | |
ArgumentException | メソッドの引数が変な場合。ArgumentNullExceptionやArgumentOutOfRangeException以外の場合で変な時に使う。 |
ArgumentNullException | 引数がnullの場合。 |
ArgumentOutOfRangeException | メソッドの許容範囲外の値が引数として渡された場合。 |
ArithmeticException | 算術演算によるエラーの基本クラス。OverflowException, DivideByZeroException, NotFiniteNumberException以外の算術エラーを示したければ使う。 |
OverflowException | 算術演算やキャストでオーバーフローが起きた場合。 |
DivideByZeroException | 0で割ったときのエラー。 |
NotFiniteNumberException | 浮動小数点値が無限大の場合。 |
FormatException | 引数の書式が仕様に一致していない場合。 |
IndexOutOfRangeException | 配列のインデックスが変な場合。 |
InvalidCastException | 無効なキャストの場合。 |
InvalidOperationException | 引数以外の原因でエラーが起きた場合。 |
ObjectDisposedException | Dispose済みのオブジェクトで操作が実行される場合。 |
NotImplementedException | メソッドが未実装の場合。 |
NotSupportedException | 呼び出されたメソッドがサポートされていない場合、または呼び出された機能を備えていないストリームに対して読み取り、シーク、書き込みが試行された場合。 |
NullReferenceException | nullオブジェクト参照を逆参照しようとした場合。 |
PlatformNotSupportException | 特定のプラットフォームで機能が実行されない場合。 |
TimeoutException | 指定したタイムアウト時間が経過した場合。 |
System.Collections.Generics 名前空間 | |
KeyNotFoundException | コレクションに該当するキーが無い場合。 |
System.IO 名前空間 | |
DirectoryNotFoundException | ディレクトリが無い場合。 |
FileNotFoundException | ファイルが無い場合。 |
EndOfStreamException | ストリームの末尾を超えて読み込もうとしている場合。 |
このあたりは、よく発生しがちで必ず考慮することを忘れないようにしましょう。
【おまけ】if文などで自分で例外処理と構文を使う際の速度の違い
tyr-catchを用いた例外処理は、if文などを使った値のチェックと比べて実行速度が遅いといわれています。
tryブロックで囲んで例外が発生しなければ、ほとんどオーバーヘッドはないのですが、 例外発生時には少し大き目のコストが発生することになります。
このコストを考えても、try-catchを使うメリットの方が明らかに大きいです。
でも、避けれるのであればコストの大きな処理は避けたいというプログラマが多いのではないでしょうか。
でもやっぱり私としては安全にtry-catchで例外処理をしてほしいと思います。
もしif文などで自分で起こりそうな例外を切り出して実装したい場合などは、下の記事を参考に条件分岐してみてください。
[C#] 条件によって処理を変えてみる(if, else)