1. はじめに
例えば、2つの数字を割り算する電卓を書いてるとするよ。全部うまく動いてるけど、誰かがゼロで割ろうとしたらどうなる?
int a = 10;
int b = 0;
int result = a / b; // ドカン!
この瞬間、プログラムは「落ちて」、ユーザーには怖いエラーメッセージが表示される。これが例外的な状況ってやつで、普通のプログラムの流れでは起きてほしくないイベントで、特別な対応が必要なんだ。
例外ってのは、.NETが「なんか変なこと起きたよ!」ってプログラムに伝えるための特別な仕組みで、処理を中断したり、特別な方法で処理したりするためのものだよ。
実行時エラー(例えばゼロで割る、存在しないファイルにアクセスしようとする、あるいはおなじみのnullを参照しちゃうとか)が起きたとき、.NETは「throw」(throw)っていう特別な例外オブジェクトを投げるんだ。それで、どこかに「catch」(catch)できるハンドラがあるか探し始める。ハンドラが見つかれば、そいつがどう対応するか決めるって流れ。
「エラーコード」を返すんじゃダメなの?
エラーコードを返す方法もあるよ。昔の言語(PascalやC、あと一部のAPIとか)はそうしてた。でもこれって結構面倒だし危険。エラーコードのチェックを忘れやすいし(みんなうまくいくって信じてるからね)、どこで何が起きたのか追いにくい。例外なら、どんなエラーも一箇所でまとめてキャッチできるし、メインのコードがエラーチェックだらけにならなくて済むんだ。
2. 例外の「throw」
例外は自動的に発生することもあるし、throwキーワードを使って自分で投げることもできるよ:
int[] arr = new int[2];
arr[10] = 5; // これは自動的にSystem.IndexOutOfRangeExceptionが投げられる
throw new Exception("ゼッタイ的カタストロフ!"); // 手動でExceptionを投げる
どんなに完璧に設計したつもりでも、予想外のことは起きる。ユーザーがファイルを閉じちゃったり、ネットが切れたり、誰かがPCの電源を落としたり(まあ、ほぼないけど)。
ありがたいことに、.NETは例外を「投げる」だけじゃなくて、ちゃんと「キャッチ」して処理する仕組みも用意してくれてる。これについては後で詳しくやるよ。
3. 例外のライフサイクル:throwからcatchまで
コードで何かヤバいことが起きたら、.NETは「緊急モード」に入るんだ:
- 例外オブジェクトを作る(例えばIndexOutOfRangeExceptionとか)。
- throwキーワードでそいつを投げる。
- ハンドラを探す:コールスタックを下から上へ(今のメソッドから呼び出し元へ)見ていって、最初に合うcatchブロックを探す。
- ハンドラが見つかったら、そのcatchブロックの中で処理が続く。
- どこにもハンドラがなければ、プログラムはクラッシュして、エラーの詳細付きの「コールスタック」を表示する。
イメージとしては、階段をボールが転がっていって、どこかでキャッチャーに受け止められるまでずっと転がり続ける感じだね。
4. .NETの例外階層
親子ツリー
.NET(他のOOP言語もそうだけど)では、例外はクラスとして実装されてて、親子関係で階層構造を作ってるんだ。
全部の例外の親は、ベースクラスのSystem.Exceptionだよ。
System.Object
└─ System.Exception
├─ System.SystemException
│ ├─ System.NullReferenceException
│ ├─ System.IndexOutOfRangeException
│ ├─ System.DivideByZeroException
│ ├─ System.OutOfMemoryException
│ └─ ... 他にもいろいろ
├─ System.IO.IOException
│ ├─ System.IO.FileNotFoundException
│ ├─ System.IO.DirectoryNotFoundException
│ └─ ...
├─ System.ArgumentException
│ ├─ System.ArgumentNullException
│ └─ System.ArgumentOutOfRangeException
└─ (自作のExceptionクラスもここにぶら下げられる)
なぜクラスなの?
クラスの階層があるおかげで、例えば関数の引数関連のエラー(ArgumentExceptionやその子孫)をまとめてキャッチできるし、自分だけのエラータイプも追加できる(これは次の講義でやるよ)。
.NETでよく見る例外たち
- NullReferenceException — nullなオブジェクトのメソッドやプロパティにアクセスしようとしたとき。おなじみ!
- DivideByZeroException — ゼロで割ったとき。
- IndexOutOfRangeException — 配列の範囲外アクセス。
- ArgumentException, ArgumentNullException, ArgumentOutOfRangeException — メソッドのパラメータが変なとき。
- FileNotFoundException, IOException — ファイル操作で問題が起きたとき。
- InvalidOperationException — オブジェクトの今の状態じゃ操作できないとき。
- FormatException — データのフォーマットが変(例えば"abcd"を数字に変換しようとしたときとか)。
5. いろんな例外が「発射」される例
わかりやすくするために、いろんな例外を発生させるコード例を見てみよう。
例1:NullReferenceException
string? str = null;
Console.WriteLine(str.Length); // ここで「ドカン!」— strはnull
出力:
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
例2:DivideByZeroException
int a = 42;
int b = 0;
int c = a / b; // 危険!ゼロで割ってる
出力:
Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.
例3:IndexOutOfRangeException
int[] numbers = { 1, 2, 3 };
Console.WriteLine(numbers[5]); // インデックス5の要素はない
明らかにインデックスが多すぎるので、出力はこうなる:
Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.
例4:FileNotFoundException
using System.IO;
string content = File.ReadAllText("secret.txt");
"secret.txt"ってファイルがなければ、こんなメッセージが出るよ:
Unhandled exception. System.IO.FileNotFoundException: Could not find file '/Users/zapp/RiderProjects/ConsoleApp1/ConsoleApp1/bin/Debug/net9.0/secret.txt'.
例5:FormatException
string input = "ジュウサン";
int number = int.Parse(input); // 文字列は数字じゃない
出力:
Unhandled exception. System.FormatException: The input string 'ジュウサン' was not in a correct format.
6. この知識をどう使う?
例外の仕組みを理解するのは、どんな開発者にとっても本当に大事。単なる「理論」じゃなくて、プログラムの安定性に直結する実践的なスキルだよ。エラーを正しく処理できれば、プログラムはすぐに「落ちる」ことなく、ユーザーに「何が起きたか」をちゃんと伝えられる。これで信頼性もプロっぽさもアップ、ユーザーにも優しいアプリになるよ。
しかも、これは便利さだけじゃない。面接でも例外処理の仕組みやExceptionの種類、.NETでどう動くかをよく聞かれるから、しっかり理解しておくと就活でも有利!
それに、実際のプロジェクトでも例外には何度も出会う。ファイル操作、ネットワーク、データベース、外部ライブラリやフレームワーク…全部例外システムをバリバリ使ってる。だから、今のうちにちゃんと仕組みを知っておくと、後で困らないよ。
7. 例外処理でよくあるミス
ミスその1:例外を完全に無視しちゃう。
初心者(時にはベテランも)は、例外を「邪魔なもの」扱いしがち。完全に無視したり、もっと悪いのはcatch { }で囲んで何もしないこと。これだとプログラムは「黙って」変な状態で動き続けて、原因追跡が超ムズくなる。
ミスその2:例外と普通のエラーを混同する。
すべてのエラーをtry-catchで捕まえようとしがちだけど、例えばユーザーが変なデータを入力した場合は、まず自分でチェックした方がいい。FormatExceptionに頼ったり、例外をロジックの一部に使うのはNG。例外は例外的なケース用で、日常的な処理じゃないよ。
ミスその3:例外階層の理解不足。
何でもかんでも親クラス(Exception)でキャッチしちゃって、全部のエラーを拾ってしまうことがある。逆に、IndexOutOfRangeExceptionやNullReferenceExceptionみたいな狭い範囲だけキャッチして、他の例外を見逃すことも。.NETの例外階層をちゃんと理解して、何をどう処理したいか考えるのが大事だよ。
そして一番大事なのは: C#の例外は「事故」や「もうダメだ!」のサインじゃなくて、特別な処理に切り替えるための仕組み。正しく使えば超強力なツールだよ。
GO TO FULL VERSION