今回はC#の例外処理について知識を深めます。
残念ながら前回の内容は実用レベルだと不十分で、本格的なプログラムを作る場合は知識が足りません。
ここではC#の例外処理について学びます。すでに少しだけ触れてますが、これは実行時エラーへの対策となります。ただ、例外処理もクラスの知識を必要とします。そのため60%くらいの解説になりますが、今はここまでいいと思ってます。 例外 (Exception) C#では実行時に発生するエラーを例外と言います。僕が知ってる他の言語も大半が同じだった気がします。この例外とは、特定の条件において処理を完了できない問題が起きると発生し、その例外を誰もcatch(キャッチ)しないとプログラムが強制終了し...
例外(Exception)
最初におさらいです。C#では実行時に発生するエラーを例外と言います。
そして、この例外を誰もcatch(キャッチ)しない場合にプログラムが強制終了します。
また、この例外処理ですが、一般的に重い処理(コストが高いと言う)に分類されます。
つまりは極論になりますが、例外処理で対策するよりも事前の分岐処理で逃げたほうが速度有利です。
じゃあ、例外は使わないほうが良いのか? みたいな話になりますが、そんなことはありません。
例外にもメリット・デメリットがあるので、それらを理解して使いましょう。ここは本題とズレるので別にします。
今回は例外処理と条件分岐の使い分けについてです。明確な答えはないので、1つの参考にしてください。また、C#を軸に話しますが、例外の仕組みを持つ他のプログラミング言語でも大抵が同じだと思います(たぶん)。 例外(Exception)とは C#では実行時に発生するエラーを例外と言い、誰もcatchしないとプログラムが強制終了します。そして、例外処理とは重い(遅い)処理に分類されます。それ故に使うか使わないかの判断で喧嘩が起こります。 こいつは最高の話題だ。切り込んで...
catch文にはException型を指定する
既に覚えた記述はこんな感じですが、こういうcatch文は殆どありません。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
string s = Console.ReadLine();
try
{
int n = int.Parse(s); // 例外の可能性がある
Console.WriteLine(n);
}
catch
{
// 例外が発生した場合の処理
Console.WriteLine("例外が発生しました。");
}
}
}
}
普通は、こんな感じにException型を指定します。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
string s = Console.ReadLine();
try
{
int n = int.Parse(s); // 例外の可能性がある
Console.WriteLine(n);
}
catch (Exception e)
{
// 変数eには例外の情報が入ってる
Console.WriteLine($"{e.Message}");
}
}
}
}
このようにException型と一緒に変数を宣言すると、発生した例外の情報が変数に入ります。
後、当たり前ですがfinally文も書けます。部分的に抜粋すると、こんな感じです。
try
{
}
catch (Exception e)
{
}
finally
{
}
Exception型
例外には種類があり、それに応じたクラスが存在します。そして、Exception型は例外の最上位クラスです。
つまり、全ての例外はException型またはException型を派生したクラスになります。
https://learn.microsoft.com/en-us/dotnet/api/system.exception
また、Exception型を派生したクラスは命名規則で、必ず〇〇Exceptionって名前になります。
先に伝えると、C#では独自の例外を作成することもでき、その場合も〇〇Exceptionって名付けるのがルールです。
付けなくてもビルドは通りますが、いわゆるゴミプログラムです。
ここではC#の継承について学びます。これはクラスの延長線の話になりますが少し難しいです。まずはクラスの基礎知識が必要なので、忘れてしまった人は以下で復習しましょう。 継承(inheritance) 継承とは、あるクラスを元に新しいクラスを定義することです。こうすることで新しく定義したクラスは元になったクラスの全ての機能を有することができます。 簡単に言えば親子関係です。親(A)を元に子(B)を定義することができます。人間なら全ての特性を引き継ぐことはありませんが、この世...
Exception型の知識
Exception型には例外に関する情報が記録されていて、所有するプロパティも大半がその情報を表現します。
仮にException型を派生した例外があったら、その例外に対する情報が付加情報として増えるイメージです。
以下はException型において、よく利用するプロパティです。このへんの値を適用に出力しておけば大抵は何とかなります。
Message | 例外に関するメッセージ |
StackTrace | メソッドの呼び出し履歴 |
TargetSite | 例外が発生したメソッド |
Source | 原因になったオブジェクトの名前 |
InnerException | 内包するExceptionインスタンス |
InnerExceptionは内包する例外の情報です。例外を発生させた後、誰もcatchせずに次の例外が発生することがあります。
そうした場合、直前の例外がInnerExceptionに格納されます。ちなみに存在しない場合の値はnullになります。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
string s = Console.ReadLine();
try
{
int n = int.Parse(s);
Console.WriteLine(n);
}
catch (Exception e)
{
Console.WriteLine($"{e.Message}");
Console.WriteLine($"{e.StackTrace}");
Console.WriteLine($"{e.TargetSite}");
Console.WriteLine($"{e.Source}");
if (e.InnerException != null)
{
Console.WriteLine($"{e.InnerException.Message}");
Console.WriteLine($"{e.InnerException.StackTrace}");
Console.WriteLine($"{e.InnerException.TargetSite}");
Console.WriteLine($"{e.InnerException.Source}");
}
}
finally
{
}
}
}
}
InnerExceptionもException型です。つまり、InnerExceptionがInnerExceptionを持つことがありえます。
よって、正しく記述するならwhile文とかを使い、内包するInnerExceptionを全て出力します。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
string s = Console.ReadLine();
try
{
int n = int.Parse(s);
Console.WriteLine(n);
}
catch (Exception e)
{
Exception tmp = e;
while (tmp != null)
{
Console.WriteLine($"{tmp.Message}");
Console.WriteLine($"{tmp.StackTrace}");
Console.WriteLine($"{tmp.TargetSite}");
Console.WriteLine($"{tmp.Source}");
tmp = tmp.InnerException;
}
}
finally
{
}
}
}
}
ぶっちゃけ、Messageだけ読めば大体なんとかなる。
一般的なException型
普通にプログラムを作ってると、頻繁に出会うException型があることに気が付きます。
以下は、みんな知ってる系のException軍団です。この辺を覚えておけば良さげ。
NullReferenceException | null参照 |
InvalidCastException | 駄目なキャスト |
IndexOutOfRangeException | 範囲外参照 |
FormatException | 何かフォーマットあってない |
ArgumentException | メソッドの引数が駄目 |
OverflowException | オーバーフロー発生 |
TimeoutException | Timeout発生 |
もっとあるけどキリがないのでここまで。
C#に標準的に用意されてる例外の紹介です。この辺の例外は事象を判断するのに便利なので、覚えておいたほうがお得ですよ。 公式ドキュメント 実はC#の公式に例外に関するドキュメントがあります。https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/using-standard-exception-types ここを読むとException型あたりは任意発生させるなって書いてあります。 ...
独自のException型
先に書いた通り、C#では独自の例外を作成できます。ただし、殆どの場合は.NET側の既存例外を使ったほうが楽です。
自作する場合はException型と同じ感覚で利用できるように、複数のコンストラクタを書きます。この辺がめんどいです。
internal class CustomException : Exception
{
public CustomException()
{
}
public CustomException(string? message)
: base(message)
{
}
public CustomException(string? message, Exception? innerException)
: base(message, innerException)
{
}
// 独自のプロパティとかメソッドを作る
}
この独自例外って、どうやったら発生するんですか?
それ専用の構文があるよ。方法は後で書くね。
複数の例外をcatchする
さて、catch文にはException型を記述すると伝えましたが、これは任意のException型を指定できます。
Exception型とは例外の最上位クラスのため、Exception型を記述する限りは全ての例外を捕捉できます。
対して、Exception型を派生したクラスを記述すると、それを満たす例外しか捕捉しなくなります。
例として、以下のようにFormatException型のみを記述すると、それを満たす例外しか捕捉しません。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
string s = Console.ReadLine();
try
{
int n = int.Parse(s);
Console.WriteLine(n);
}
catch (FormatException e)
{
}
}
}
}
ですが、これだとFormatException型に関する例外しか補足できません。
他の例外、例えばNullReferenceException型とかが発生すると死にます。
それでは困るので、普通は複数のcatch文を書いて、対応すべき全ての例外を処理します。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
string s = Console.ReadLine();
try
{
int n = int.Parse(s);
Console.WriteLine(n);
}
catch (FormatException e)
{
// FormatException型が発生した場合の処理
}
catch (NullReferenceException e)
{
// NullReferenceException型が発生した場合の処理
}
}
}
}
こうすることで目的の例外が発生した場合の処理を分岐できます。
例えば入力値が問題なら、それをユーザーに教えて直してもらうとかできます。
この時の注意点ですが、複数のcatch文を書く場合、先に条件を満たす例外を記述することはできません。
つまり、こういうことです。この場合は常にException型を満たすため、絶対に後半に進みません。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
string s = Console.ReadLine();
try
{
int n = int.Parse(s);
Console.WriteLine(n);
}
catch (Exception e)
{
}
catch (FormatException e)
{
}
}
}
}
こういうのはビルドエラーになります。解決先は単に順番を入れ替えればいいです。
throw文
C#には独自の例外を含めて、任意に例外を発生(投げると言う)させる方法があります。
それがthrow文です。構文は以下となり、発生させたい例外のインスタンスを生成します。
throw new 例外インスタンス;
普通はこんな感じに何らかのメッセージを記述します。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
try
{
throw new Exception("任意に例外を発生");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
インスタンスを生成せず既存の例外を投げることもできます。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
try
{
try
{
throw new Exception("任意に例外を発生");
}
catch (Exception e)
{
// catchで捕捉した例外を投げることも可能
throw; // 例外インスタンスは指定しない
// catchの中で元の例外インスタンスを指定すると情報が上書きされる。
// この書き方はビルド時に警告が出るため、基本的に使う必要はない。
//throw e;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
独自例外もthrowを使って発生させることができます。
あとがき
例外発生時のポイントは、その後もプログラムを継続するか否かです。一般的に回復が可能なら、それに準じる処理を行い継続します。
それが無理ならログ等を残した後、問題となる事象をユーザーに伝え、自発的にプログラムを終了します。
1番駄目なのは、正しく回復できてないのに処理を進めること。こういうことをすると後に甚大な被害を生みます。
諦めるのも大切ってことだね。
◆ C#に関する学習コンテンツ
この記事は参考になりましたか?
コメント