第15章 例外処理
この章の目的
Section titled “この章の目的”この章では、プログラムの実行中に発生するエラーに対応するための 例外処理(exception handling)を学習します。
これまでに何度も書いてきた次のコードを思い出してください。
string input = Console.ReadLine();int number = int.Parse(input);正しく数値が入力されれば問題なく動きますが、abc のような文字列が入力されると 実行中にエラー になります。
ファイル操作や DB 接続でも、同じように「実行してみないと分からない問題」がよく起こります。
入力ミス(数値以外、空文字)ファイルが存在しない・アクセスできないDB に接続できない・SQL が間違っているネットワークが切れているこのような実行時の問題に プログラムを落とさず安全に対応 する仕組みが、例外処理です。 特に、第 17 章以降の DB 接続では、例外処理が 必須 になります。
この章では次のことを目指します。
例外とは何かを知るtry-catch の基本形を使える例外が発生してもプログラムを終了させずに対応できる例外の種類を見分けられるfinally の役割を理解する入力エラー・ファイル操作エラーに対応できるこの章でできるようになること
Section titled “この章でできるようになること”この章を終えると、次のことができるようになります。
- 例外とは何かを説明できる
- 実行時エラーとコンパイルエラーの違いをおおまかに説明できる
try-catchの基本形を書けるFormatExceptionなどの代表的な例外クラスを知っているExceptionオブジェクトからエラーメッセージを取得できる- 複数の
catchを使って例外の種類ごとに処理を分けられる catchの順序ルール(具体的な例外を先に)を説明できるfinallyの役割を説明できるint.TryParseで安全に数値入力を処理できるthrowで自分で例外を発生させられる- ファイル操作・DB 接続で例外処理が重要になる理由を説明できる
本章で使用する環境
Section titled “本章で使用する環境”| 項目 | 内容 |
|---|---|
| 開発環境 | Visual Studio 2022 |
| プロジェクト種類 | コンソール アプリ |
| 対象フレームワーク | .NET 8 |
| ソリューション名 | Chapter15 |
| プロジェクト名 | Ch15_ExceptionHandling |
csproj の Nullable は disable に変更してください
プロジェクト作成後、
Ch15_ExceptionHandling.csprojを開き、<Nullable>disable</Nullable>に変更してください。詳しい手順は、第 1 章「1-1 プロジェクトを作成する」を参照してください。
作業前チェック
Section titled “作業前チェック”作業を始める前に、次の内容を確認してください。
-
Console.ReadLineで入力を受け取れる -
int.Parseで文字列を数値に変換できる -
if文・while文を書ける - メソッドを作成できる
- ファイルの読み書き(
Fileクラス)の基本(第 9 章) - 直前の章までの内容を Git に提出済みである
15-1 例外とは
Section titled “15-1 例外とは”コンパイルエラーと実行時エラー
Section titled “コンパイルエラーと実行時エラー”プログラムのエラーには大きく 2 種類あります。
| 種類 | 発生タイミング | 例 |
|---|---|---|
| コンパイルエラー | 実行する前(VS の編集中に発見) | セミコロン抜け、スペルミス、型不一致 |
| 実行時エラー(例外) | 実行中 | 数値変換失敗、ファイル不存在、0 除算 |
コンパイルエラーは VS が赤波線で教えてくれるので比較的気付けます。 例外は実行してみないと分からない ため、対応を準備しておく必要があります。
例外を起こしてみる
Section titled “例外を起こしてみる”次のコードを Program.cs に貼って実行してください。
namespace Ch15_ExceptionHandling;
internal class Program{ static void Main(string[] args) { Console.Write("整数を入力してください:"); string input = Console.ReadLine(); int number = int.Parse(input);
Console.WriteLine($"入力された数値:{number}"); }}数値(例:123)を入力すれば正常に動きます。
しかし、abc のような文字列を入力すると例外が発生 します。
実行例(abc を入力):
整数を入力してください:abcハンドルされていない例外: System.FormatException: The input string 'abc' was not in a correct format. 場所 ...このように、プログラムが途中で強制終了 してしまいました。
発生した例外の名前は FormatException です。
代表的な例外クラス
Section titled “代表的な例外クラス”C# には目的別にたくさんの例外クラスが用意されています。 すべてを覚える必要はありませんが、よく出会うものを挙げます。
| 例外クラス | 主な発生シーン |
|---|---|
FormatException | int.Parse などで数値変換に失敗 |
DivideByZeroException | 整数を 0 で割った |
IndexOutOfRangeException | 配列の範囲外にアクセスした(第 6 章) |
NullReferenceException | null の変数に .プロパティ でアクセスした(第 11 章) |
FileNotFoundException | 指定したファイルが見つからない |
IOException | ファイル入出力で問題が起きた |
ArgumentException | 引数が不正(自分で throw するときにも使える) |
InvalidOperationException | 操作できない状態で処理を呼んだ |
例外には種類があり、後で見るように 種類ごとに対応を分けられる のが C# の例外処理の特徴です。
15-2 try-catch の基本形
Section titled “15-2 try-catch の基本形”try-catch の構造
Section titled “try-catch の構造”例外処理の基本形は try-catch です。
try{ // 例外が発生する可能性がある処理}catch{ // 例外が発生したときの処理}try ブロックの中で例外が発生すると、その時点で try 内の処理は中断され、catch ブロックへ処理が移ります。
例外が発生しなければ、catch は実行されません。
数値変換エラーをつかまえる
Section titled “数値変換エラーをつかまえる”先ほどのコードを try-catch で囲み、abc を入れてもプログラムが落ちないようにします。
namespace Ch15_ExceptionHandling;
internal class Program{ static void Main(string[] args) { Console.Write("整数を入力してください:"); string input = Console.ReadLine();
try { int number = int.Parse(input); Console.WriteLine($"入力された数値:{number}"); } catch { Console.WriteLine("整数に変換できませんでした。"); }
Console.WriteLine("処理を終了します。"); }}実行例(abc を入力):
整数を入力してください:abc整数に変換できませんでした。処理を終了します。実行例(123 を入力):
整数を入力してください:123入力された数値:123処理を終了します。例外は発生していますが、catch で受け止めているので プログラム全体は最後まで進みます。
catch で例外情報を受け取る
Section titled “catch で例外情報を受け取る”catch (Exception ex) の形で書くと、例外オブジェクトを受け取れます。
ex.Message で 発生した例外の説明文 を取得できます。
namespace Ch15_ExceptionHandling;
internal class Program{ static void Main(string[] args) { Console.Write("整数を入力してください:"); string input = Console.ReadLine();
try { int number = int.Parse(input); Console.WriteLine($"入力された数値:{number}"); } catch (Exception ex) { Console.WriteLine("エラーが発生しました。"); Console.WriteLine($"内容:{ex.Message}"); }
Console.WriteLine("処理を終了します。"); }}実行例(abc を入力):
整数を入力してください:abcエラーが発生しました。内容:The input string 'abc' was not in a correct format.処理を終了します。ex.Message の文言は環境やバージョンで多少変わりますが、何が起きたか を教えてくれます。
研修中はこのメッセージで原因を確認できます。
補足:
Exceptionは例外の親玉
Exceptionは、すべての例外クラスの 親(基底クラス) です。FormatExceptionもIOExceptionも、このExceptionを親とする継承関係にあります(継承については第 13 章で扱います)。catch (Exception ex)は「何でも捕まえる」という意味になります。
15-3 複数の catch で種類別に対応する
Section titled “15-3 複数の catch で種類別に対応する”例外の種類で処理を分ける
Section titled “例外の種類で処理を分ける”try の後ろに catch を 複数並べる ことができます。
例外の種類に応じて違う処理をしたい場合に使います。
try{ 処理}catch (FormatException){ 数値変換エラーの場合}catch (Exception ex){ その他のエラーの場合}0 除算と数値変換を両方扱う
Section titled “0 除算と数値変換を両方扱う”割られる数と割る数を入力し、結果を表示するプログラムです。
- 数値以外を入力した場合 →
FormatException - 割る数に
0を入れた場合 →DivideByZeroException
両方を別々の catch で扱います。
namespace Ch15_ExceptionHandling;
internal class Program{ static void Main(string[] args) { try { Console.Write("割られる数を入力してください:"); int number1 = int.Parse(Console.ReadLine());
Console.Write("割る数を入力してください:"); int number2 = int.Parse(Console.ReadLine());
int result = number1 / number2; Console.WriteLine($"計算結果:{result}"); } catch (FormatException) { Console.WriteLine("整数として正しい形式で入力してください。"); } catch (DivideByZeroException) { Console.WriteLine("0 で割ることはできません。"); } catch (Exception ex) { Console.WriteLine("予期しないエラーが発生しました。"); Console.WriteLine($"内容:{ex.Message}"); } }}実行例 1(数値以外を入力):
割られる数を入力してください:abc整数として正しい形式で入力してください。実行例 2(0 を入力):
割られる数を入力してください:10割る数を入力してください:00 で割ることはできません。実行例 3(正常):
割られる数を入力してください:10割る数を入力してください:3計算結果:3catch の順序ルール
Section titled “catch の順序ルール”複数の catch を書くときは、具体的な例外を先、Exception を後 にします。
// OKcatch (FormatException) { ... }catch (DivideByZeroException) { ... }catch (Exception ex) { ... }逆順に書くと、Exception がすべての例外を捕まえてしまい、後ろの具体的な catch に 永久に到達しなくなります。
// NGcatch (Exception ex) { ... } // ← ここですべて捕まえるcatch (FormatException) { ... } // ← ここには到達しない(警告/エラー)C# のコンパイラがこのパターンに警告を出してくれます。
15-4 finally で必ず実行する処理
Section titled “15-4 finally で必ず実行する処理”finally とは
Section titled “finally とは”finally は、try-catch のあとに付ける特別なブロックで、例外が発生してもしなくても、最後に必ず実行されます。
try{ 処理}catch (Exception ex){ 例外が発生したときの処理}finally{ 必ず実行する処理(後片付け)}主な使い道は、後片付け処理です。
ファイルを閉じるDB 接続を閉じるロックを解放するログを出すfinally の動きを確認する
Section titled “finally の動きを確認する”namespace Ch15_ExceptionHandling;
internal class Program{ static void Main(string[] args) { Console.Write("整数を入力してください:"); string input = Console.ReadLine();
try { int number = int.Parse(input); Console.WriteLine($"入力された数値:{number}"); } catch (FormatException) { Console.WriteLine("整数として正しい形式で入力してください。"); } finally { Console.WriteLine("入力処理を終了します。"); }
Console.WriteLine("プログラムを終了します。"); }}実行例(正常):
整数を入力してください:123入力された数値:123入力処理を終了します。プログラムを終了します。実行例(異常):
整数を入力してください:abc整数として正しい形式で入力してください。入力処理を終了します。プログラムを終了します。正常時も異常時も、「入力処理を終了します。」 が必ず表示されます。
DB 接続との関係(後章への布石)
Section titled “DB 接続との関係(後章への布石)”後の DB 接続編では、次のような構造で書くことが多くなります。
DB に接続するSQL を実行する結果を読み取るDB 接続を閉じる ← 例外が出ても忘れたくないDB 接続は 開いたまま放置すると、サーバー側のリソースが枯渇する など重大な問題になります。
そのため、finally で必ず閉じる、というのが昔からのパターンです。
ただし、現代の C# では using 文 を使って自動で閉じる書き方も使います。
これは第 17 章以降で扱います。
15-5 TryParse で例外を出さずに変換する
Section titled “15-5 TryParse で例外を出さずに変換する”例外を出すのは「コスト」
Section titled “例外を出すのは「コスト」”int.Parse + try-catch で数値変換エラーに対応できますが、入力チェック程度のことに毎回例外を発生させるのは大げさ という考え方もあります。
例外には、内部的に「どこで起きたか」「どう呼ばれたか」などの情報を集める処理が走るため、それなりに重い のです。
そこで、入力チェックには int.TryParse が便利です。
| 方法 | 失敗時 | 向いている場面 |
|---|---|---|
int.Parse | 例外が発生する | 入力が正しい前提の内部処理 |
int.TryParse | false を返すだけ(例外なし) | ユーザー入力のチェック |
TryParse の使い方
Section titled “TryParse の使い方”int.TryParse は、変換に成功したかを bool で返し、変換結果は out パラメータ で受け取ります。
int number;bool success = int.TryParse(input, out number);| 結果 | success | number |
|---|---|---|
"123" | true | 123 |
"abc" | false | 0(初期値) |
out は「変換できた値を、この変数に書き込んでください」という指定です。
よく使う書き方として、変数宣言を out の中に書く短縮形もあります。
bool success = int.TryParse(input, out int number);TryParse を使った数値入力
Section titled “TryParse を使った数値入力”namespace Ch15_ExceptionHandling;
internal class Program{ static void Main(string[] args) { Console.Write("整数を入力してください:"); string input = Console.ReadLine();
if (int.TryParse(input, out int number)) { Console.WriteLine($"入力された数値:{number}"); } else { Console.WriteLine("整数として正しい形式で入力してください。"); } }}実行例(正常):
整数を入力してください:123入力された数値:123実行例(異常):
整数を入力してください:abc整数として正しい形式で入力してください。try-catch を使わずに、エラーを処理できました。
正しい数値が入力されるまで再入力させる
Section titled “正しい数値が入力されるまで再入力させる”while (true) と組み合わせると、正しい数値が入力されるまで何度も聞き直す プログラムになります。
namespace Ch15_ExceptionHandling;
internal class Program{ static void Main(string[] args) { int number;
while (true) { Console.Write("整数を入力してください:"); string input = Console.ReadLine();
if (int.TryParse(input, out number)) { break; }
Console.WriteLine("整数として正しい形式で入力してください。"); }
Console.WriteLine($"入力された数値:{number}"); }}実行例:
整数を入力してください:abc整数として正しい形式で入力してください。整数を入力してください:12.5整数として正しい形式で入力してください。整数を入力してください:42入力された数値:42このパターンは、ユーザー入力を扱うコンソールアプリで頻繁に使います。
15-6 throw で自分から例外を発生させる
Section titled “15-6 throw で自分から例外を発生させる”throw とは
Section titled “throw とは”プログラムの中で、あえて例外を発生させたいことがあります。 たとえば、商品価格にマイナスが指定された場合 ── プログラムとしては明らかに不正です。
このようなときに throw を使います。
throw new ArgumentException("価格にマイナス値は指定できません。");これは、第 7 章で学んだ new クラス名(...) の形と同じです。例外オブジェクトを作って throw で投げます。
Product クラスで価格をチェックする
Section titled “Product クラスで価格をチェックする”Product.cs:
namespace Ch15_ExceptionHandling;
class Product{ public string ProductName { get; private set; } public int Price { get; private set; }
public Product(string productName, int price) { if (price < 0) { throw new ArgumentException("価格にマイナス値は指定できません。"); }
ProductName = productName; Price = price; }
public string GetDisplayText() { return $"{ProductName}:{Price}円"; }}Program.cs:
namespace Ch15_ExceptionHandling;
internal class Program{ static void Main(string[] args) { try { Product product = new Product("ノート", -100); Console.WriteLine(product.GetDisplayText()); } catch (ArgumentException ex) { Console.WriteLine("引数エラーが発生しました。"); Console.WriteLine($"内容:{ex.Message}"); } }}実行結果:
引数エラーが発生しました。内容:価格にマイナス値は指定できません。-100 という不正な価格を渡したので、Product のコンストラクター内で例外が発生し、Main 側の catch で受け止めています。
例外と if 文の使い分け
Section titled “例外と if 文の使い分け”例外は便利ですが、何でも例外で処理するのはやり過ぎ です。
ユーザーがよく入力ミスをするケース → if 文・TryParse で対応する(例外を出さない)
「呼び出し方が間違っている」「ありえない状態」 → 例外として throw するたとえば、上記の Product クラスでは、コンストラクターを呼ぶ側が「マイナス価格は渡さない」のが当然です。
それでもマイナスが渡されたら、プログラムの呼び出し方が間違っている ので例外で知らせます。
一方、ユーザーが Console.ReadLine でうっかり数字以外を入力するのは「よくあること」なので、TryParse で淡々とエラー表示すれば十分です。
15-7 ファイル操作と例外処理
Section titled “15-7 ファイル操作と例外処理”File 操作で起こりうる例外
Section titled “File 操作で起こりうる例外”第 9 章で File.ReadAllLines などを学びました。
ファイル操作では、次のような例外が起こり得ます。
| 例外 | 発生シーン |
|---|---|
FileNotFoundException | 指定したファイルが見つからない |
DirectoryNotFoundException | 指定したフォルダが見つからない |
UnauthorizedAccessException | アクセス権がない |
IOException | その他のファイル入出力エラー(他プロセスが使用中など) |
File.Exists で事前確認しても、確認直後に削除される可能性があるため、try-catch での例外対応も併用 するのが安全です。
ファイル読み込みの例外処理
Section titled “ファイル読み込みの例外処理”namespace Ch15_ExceptionHandling;
internal class Program{ static void Main(string[] args) { string filePath = "employee_names.txt";
try { string[] lines = File.ReadAllLines(filePath);
foreach (string line in lines) { Console.WriteLine(line); } } catch (FileNotFoundException) { Console.WriteLine("ファイルが見つかりません。"); } catch (Exception ex) { Console.WriteLine("ファイル読み込み中にエラーが発生しました。"); Console.WriteLine($"内容:{ex.Message}"); } }}実行例(ファイルが存在しない場合):
ファイルが見つかりません。employee_names.txt を bin\Debug\net8.0\ フォルダに置いてから実行すると、内容が表示されます。
ファイル書き込みの例外処理
Section titled “ファイル書き込みの例外処理”namespace Ch15_ExceptionHandling;
internal class Program{ static void Main(string[] args) { string filePath = "app.log"; string text = $"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")} アプリを実行しました。{Environment.NewLine}";
try { File.AppendAllText(filePath, text); Console.WriteLine("ログを書き込みました。"); } catch (IOException ex) { Console.WriteLine("ファイル書き込み中にエラーが発生しました。"); Console.WriteLine($"内容:{ex.Message}"); } catch (Exception ex) { Console.WriteLine("予期しないエラーが発生しました。"); Console.WriteLine($"内容:{ex.Message}"); } }}実行結果:
ログを書き込みました。IOException を先に書き、その他を Exception で受ける形です。
15-8 DB 接続に向けた例外処理の考え方
Section titled “15-8 DB 接続に向けた例外処理の考え方”DB 接続で起こりうる問題
Section titled “DB 接続で起こりうる問題”第 17 章以降の DB 接続では、次のような問題が常に起こりえます。
SQL Server が起動していない接続文字列が間違っているユーザー名・パスワードが違うネットワーク・サービスの問題SQL 文の文法ミス、テーブル名・列名の間違い取得した値が想定と違う(NULL 含む)DB 接続では、実行時の問題に必ず備える必要があります。
後の章で書くことになる構造
Section titled “後の章で書くことになる構造”実際の DB 接続コードは第 17 章で扱いますが、構造のイメージは次のようになります。
try{ // DB に接続する // SQL を実行する // 結果を読み取る}catch (Exception ex){ // エラー内容を表示する(またはログに残す)}finally{ // 必要に応じて DB 接続を閉じる}using 文を使えば finally を書かずに自動で閉じる書き方もあります(第 17 章で扱う)。
押さえておきたい考え方:
DB 接続は失敗することがある失敗したときに、プログラムがただ落ちるだけでは困るエラー内容を確認できるようにするリソース(接続)は必ず解放する業務アプリでは例外メッセージを表に出しすぎない
Section titled “業務アプリでは例外メッセージを表に出しすぎない”研修中は学習のため ex.Message を画面に表示します。
しかし、業務アプリでは利用者にそのまま見せないのが普通 です。
例外メッセージには、次のような情報が含まれることがあります。
接続文字列(サーバー名・ユーザー名)SQL 文・テーブル名・列名内部のシステム情報これが利用者に見えると、システムの内部構造が漏れたり、ユーザーが混乱したりします。 業務アプリでは次のような対応が一般的です。
| 出力先 | 内容 |
|---|---|
| 利用者向け画面 | 「データの取得に失敗しました。管理者に連絡してください。」 |
| 開発者向けログ | 例外メッセージ・スタックトレース・呼び出し元情報 |
研修では ex.Message を確認しますが、本番想定のときは表示方法を考える、と覚えておきましょう。
よくあるつまずき
Section titled “よくあるつまずき”| つまずき | 原因 | 対応 |
|---|---|---|
| 例外処理の必要性が分からない | 正常系だけで考えている | 入力ミスやファイル不存在を想定する |
try の中に何を書くか分からない | 例外が発生しそうな処理を意識できていない | 変換、ファイル、DB 接続などを入れる |
catch が実行されない | 例外が発生していない | 正常時は catch に入らない |
catch (Exception) だけにしてしまう | 例外の種類を分けていない | 必要に応じて具体的な例外を先に書く |
| catch の順番でエラー | Exception を先に書いている | 具体的な例外を先、Exception を後 |
finally の意味が分からない | 正常時も異常時も実行されることを知らない | 後片付け処理を書く場所と覚える |
TryParse の out が分からない | 変換結果の受け取り方に慣れていない | 「成功時に値を入れる箱」と覚える |
| 例外を握りつぶしてしまう | catch だけ書いて何もしない | 最低限メッセージ表示やログ出力を行う |
| 何でも例外で処理しようとする | 条件分岐との使い分けが曖昧 | 入力チェックは TryParse や if も使う |
throw のあとに何か続けて書く | throw でメソッドが終わると気付いていない | throw 以降のコードは到達しない |
学んだことチェック
Section titled “学んだことチェック”- 例外とは何かを説明できる
- コンパイルエラーと実行時エラーの違いを説明できる
-
try-catchの基本形を書ける -
FormatException・DivideByZeroExceptionなどの代表的な例外を説明できる -
ExceptionのMessageを表示できる - 複数の
catchを順序ルールに従って書ける -
finallyの役割を説明できる -
int.TryParseを使って安全に数値入力を処理できる -
int.TryParseとwhileで再入力ループを書ける -
throwで例外を発生させられる - ファイル読み書きの例外を処理できる
- DB 接続で例外処理が重要になる理由を説明できる
- 業務アプリでは例外メッセージの扱いに注意することを説明できる
研修の進め方によっては、隣の人またはチーム内で説明確認を行います。
次の内容を、自分の言葉で説明してください。
- 例外とは何ですか。
tryにはどんな処理を書きますか。catchはいつ実行されますか。FormatExceptionはどんなときに発生しますか。finallyは何のために使いますか。int.Parseとint.TryParseの違いは何ですか。throwはどんなときに使いますか。- DB 接続で例外処理が重要になるのはなぜですか。
- 業務アプリで例外メッセージをそのまま画面に出さない方がよい理由は何ですか。
説明するときは、完全な答えでなくても構いません。 自分の言葉で説明しようとすることが大切です。
この章の演習課題に取り組みます。
本章では タイマー方式 で進めます。次の 3 段階で取り組んでください。
| 段階 | 時間 | 内容 |
|---|---|---|
| ① 準備 | 10 分(目安) | 上の「ペア確認」と、これから取り組む課題(仕様)の読み込み。ペアや講師に質問してよい |
| ② ソロ作業 | 35 分(タイマーで計測) | 一人で課題に取り組む。タイマーが鳴ったら、完成・未完成にかかわらず作業を止めて提出する |
| ③ チーム時間 | 講師が指定する発表開始時刻まで | チーム内でコードレビューを行い、発表者を決める。実装の続行も可。時間配分はチームで管理する |
評価対象はタイマー時点で提出されたコードです(タイマー後に書き足した分は評価には含まれません)。 発表開始時刻は厳守 です。チーム時間中も、その時刻が来たら全員手を止めて発表に移ります。
演習の進め方の詳細は、付録A「演習の進め方」 を参照してください。
この章の演習の進め方
Section titled “この章の演習の進め方”課題はソリューション Kadai15 の中に作成してください。
課題ごとに別のプロジェクトを作成 し、指定されたプロジェクト名を使います。
| 課題 | 必須/発展 | プロジェクト名 | 作成する主なファイル |
|---|---|---|---|
| 課題 15-1 | 必須 | Kd15_01_TryCatchBasic | Program.cs |
| 課題 15-2 | 必須 | Kd15_02_MultipleCatch | Program.cs |
| 課題 15-3 | 必須 | Kd15_03_TryParseInput | Program.cs |
| 課題 15-4 | 発展 | Kd15_04_RetryInput | Program.cs |
| 課題 15-5 | 発展 | Kd15_05_ProductThrow | Program.cs、Product.cs |
| 課題 15-6 | 発展 | Kd15_06_FileException | Program.cs |
フォルダ構成は次のようになります。
Kadai15/ ← 課題用ソリューションフォルダ Kadai15.sln Kd15_01_TryCatchBasic/ Kd15_02_MultipleCatch/ Kd15_03_TryParseInput/ Kd15_04_RetryInput/ Kd15_05_ProductThrow/ Kd15_06_FileException/補足:ソリューションに複数のプロジェクトを追加する方法
最初の課題で
Kadai15ソリューションとKd15_01_TryCatchBasicプロジェクトを同時に作成します。2 つ目以降の課題は、ソリューションエクスプローラーで
Kadai15を右クリックし、追加→新しいプロジェクトから追加します。
演習の共通ルール
Section titled “演習の共通ルール”以下は、本章のすべての課題に共通する作業です。各課題の本文には繰り返し書きません。
| 作業 | 内容 | 参照 |
|---|---|---|
| プロジェクト作成時の設定 | 「最上位レベルのステートメントを使用しない」にチェック | 第 1 章 1-1 |
| csproj の編集 | <Nullable>disable</Nullable> に変更 | 第 1 章 1-1 |
namespace の書き方 | ファイルスコープ形式(namespace XXX;)で書く | 第 7 章 冒頭 |
| クラスファイルの追加 | ソリューションエクスプローラーでプロジェクトを右クリック → 追加 → クラス(課題 15-5 の Product.cs など) | 第 7 章 7-7 |
| ファイルを保存して実行 | Ctrl + S で保存 → F5 で実行 | 第 1 章 1-2 |
特に 2 つ目以降のプロジェクトも、新規追加するたびに csproj の Nullable を disable に変更する 必要があります。
提出ルール(タイマー方式)
35 分のタイマーが鳴ったら手を止め、
git add→commit→pushを行います。評価対象はタイマー時点のコミットのみです(タイマー後の追加は評価外)。Chapter15 タイマー提出: <どこまで完成> / <詰まったポイント>例:
Chapter15 タイマー提出: 15-1〜15-2完成、15-3は途中 / TryParse の out の書き方で詰まった
サーバへコピー提出の場合
Git 不調で講師指示があれば、
pushの代わりにKadai15フォルダをサーバへコピーし、提出先に提出メモ.txtを作成します(内容:どこまで完成 / 詰まったポイント)。
タイマー後のチーム時間
発表開始時刻まで、コードレビュー / 発表者選出 / 実装続行(任意・評価外)。時間配分はチームの判断、発表開始時刻は厳守。
まずは、全員が必須課題に取り組んでください。
課題 15-1 try-catch で数値変換エラーに対応する
Section titled “課題 15-1 try-catch で数値変換エラーに対応する”整数を入力し、int.Parse で変換するプログラムを try-catch で囲んでください。
数値以外が入力された場合は、エラーメッセージを表示して、プログラムは正常に終了するようにしてください。
実行例(正常):
整数を入力してください:123入力された数値:123処理を終了します。実行例(異常):
整数を入力してください:abc整数として正しい形式で入力してください。処理を終了します。条件:
try-catchを使うcatch (FormatException)を使う- 例外発生後も「処理を終了します。」が表示されること
課題 15-2 複数の catch + finally で割り算
Section titled “課題 15-2 複数の catch + finally で割り算”2 つの整数を入力し、割り算の結果を表示してください。
次の場合をそれぞれ別の catch で処理し、最後に finally で必ずメッセージを表示してください。
| 状況 | 例外 | 表示するメッセージ |
|---|---|---|
| 数値以外を入力 | FormatException | 「整数として正しい形式で入力してください。」 |
| 割る数に 0 を入力 | DivideByZeroException | 「0 で割ることはできません。」 |
| その他のエラー | Exception | 「予期しないエラーが発生しました。」 + ex.Message |
| 必ず | (なし) | finally で「計算処理を終了します。」 |
実行例(正常):
割られる数を入力してください:10割る数を入力してください:3計算結果:3計算処理を終了します。実行例(0 入力):
割られる数を入力してください:10割る数を入力してください:00 で割ることはできません。計算処理を終了します。条件:
catch (FormatException)→catch (DivideByZeroException)→catch (Exception ex)の 順番 を守るfinallyで必ず終了メッセージを出す
課題 15-3 TryParse で安全に数値入力する
Section titled “課題 15-3 TryParse で安全に数値入力する”int.TryParse を使って、整数を 1 つ入力するプログラムを作成してください。
try-catch は使わないでください。
実行例(正常):
整数を入力してください:42入力された数値:42実行例(異常):
整数を入力してください:abc整数として正しい形式で入力してください。条件:
int.TryParse(input, out int number)を使うif (...) { ... } else { ... }で分岐するtry-catchを 使わない
必須課題が終わった人は、発展課題に取り組んでください。 発展課題からは、仕様だけが提示されます。実装方法は自分で考えてください。
課題 15-4 正しい年齢が入力されるまで再入力させる
Section titled “課題 15-4 正しい年齢が入力されるまで再入力させる”ユーザーから「年齢」を入力してもらうプログラムを作成してください。 正しい範囲の整数(0 以上 150 以下)が入力されるまで、何度でも聞き直すようにします。
仕様
- メッセージは「年齢を入力してください:」
- 入力チェック:
- 整数に変換できない場合 → 「整数として正しい形式で入力してください。」
- 0 未満または 150 超 → 「0 以上 150 以下で入力してください。」
- 正しく入力できたら「入力された年齢:N」と表示して終了
実装ヒント
while (true)+int.TryParse+ifで範囲チェック- 範囲外なら
continueで再入力に戻す
実行例:
年齢を入力してください:abc整数として正しい形式で入力してください。年齢を入力してください:2000 以上 150 以下で入力してください。年齢を入力してください:25入力された年齢:25課題 15-5 Product クラスで不正な価格を例外にする
Section titled “課題 15-5 Product クラスで不正な価格を例外にする”商品を表す Product クラスを別ファイル(Product.cs)で作成し、コンストラクターで価格をチェックしてください。
価格がマイナスの場合は ArgumentException を投げ、Main 側で catch してください。
Product クラス仕様
- ファイル名:
Product.cs - プロパティ:
ProductName(string)、Price(int)、どちらもget; private set; - コンストラクター:
Product(string productName, int price)price < 0ならthrow new ArgumentException("価格にマイナス値は指定できません。");
- メソッド:
GetDisplayText()で"{ProductName}:{Price}円"を返す
Main メソッド仕様
tryブロックの中で、次の 2 通りを順に実行するnew Product("ノート", 180)を作成して表示new Product("消しゴム", -50)を作成して表示(ここで例外発生)
catch (ArgumentException ex)でエラーメッセージを表示
実行結果例:
ノート:180円引数エラーが発生しました。内容:価格にマイナス値は指定できません。課題 15-6 ファイル読み書きの例外処理
Section titled “課題 15-6 ファイル読み書きの例外処理”employee_names.txt を読み込んで内容を表示し、その後 app.log に「読み込み完了:現在日時」を追記するプログラムを作成してください。
ファイル操作で起こりうる例外を、複数の catch で適切に処理します。
仕様
- 読み込みフェーズ:
File.ReadAllLines("employee_names.txt")で読み込む- 全行を
foreachで表示 FileNotFoundException→ 「ファイルが見つかりません。」- その他の例外 → 「読み込み中にエラーが発生しました。」+
ex.Message
- 書き込みフェーズ:
File.AppendAllText("app.log", $"読み込み完了:{DateTime.Now:yyyy/MM/dd HH:mm:ss}{Environment.NewLine}")IOException→ 「ログ書き込み中にエラーが発生しました。」+ex.Message- その他の例外 → 「予期しないエラーが発生しました。」+
ex.Message
- それぞれ別の
try-catchで書く(1 つの大きなtryにまとめない) - 最後に
finallyで「処理を終了します。」と表示するtry-catch-finallyを全体の外側に書く必要はない(各フェーズのtry-catchの後に普通に書けば OK)
事前準備
bin\Debug\net8.0\ フォルダに employee_names.txt を作成し、社員名(山田二郎、佐藤昭夫、山口洋子 など)を 3 行書いておく。
ファイルがない状態でも実行して、FileNotFoundException の動きも確認すること。
実行例(ファイル有):
山田二郎佐藤昭夫山口洋子ログを書き込みました。処理を終了します。実行例(ファイル無):
ファイルが見つかりません。ログを書き込みました。処理を終了します。提出前チェックリスト
Section titled “提出前チェックリスト”- プログラムを Visual Studio から実行できる
-
try-catchを使えている -
FormatException・DivideByZeroExceptionを使い分けている(必須 15-2) -
catchの順序ルール(具体例外を先)を守れている -
finallyを使えている(必須 15-2) -
int.TryParseを使えている(必須 15-3) -
whileとTryParseで再入力ループを書けている(発展) -
throwで例外を発生させている(発展) - ファイル例外(
FileNotFoundException、IOException)を処理している(発展) - 例外メッセージを
ex.Messageで確認している - インデントが整っている
- 課題を提出した(Git の
commit→push、または講師指示があればKadai15フォルダをサーバへコピーし、提出先フォルダに提出メモ.txtを作成)
Git への提出
Section titled “Git への提出”タイマーが鳴ったら、第 15 章の課題プロジェクト群(Kadai15 ソリューション)をその場で Git に提出します。
git statusgit add .git commit -m "Chapter15 タイマー提出: <どこまで完成> / <詰まったポイント>"git push origin main実行例:
git commit -m "Chapter15 タイマー提出: 15-1〜15-3完成、15-4は途中 / 範囲チェックの再入力ループで詰まった"Git の詳しい操作は 付録 C、コミットメッセージ形式とサーバコピー提出は本章冒頭「演習課題 > 提出ルール」を参照してください。
この章のまとめ
Section titled “この章のまとめ”この章では、例外処理を学習しました。
- 例外 は、プログラムの実行中に発生する問題
- 例外の種類:
FormatException・DivideByZeroException・FileNotFoundException・IOExceptionなど try-catchで例外を捕まえ、プログラムが落ちないようにできるcatch (Exception ex)で例外オブジェクトを受け取り、ex.Messageで内容を確認できる- 複数の
catchを書けるが、具体的な例外を先・Exceptionを後 にする finallyは、例外の有無にかかわらず最後に必ず実行される(後片付けに使う)int.TryParseを使えば、例外を出さずに変換可否をチェックできるint.TryParse+whileで、再入力させる定番パターンを書けるthrow new ArgumentException(...)で自分から例外を発生させられる- 例外と
if文の使い分け:よくある入力ミスはTryParse、ありえない状態は例外 - ファイル操作・DB 接続では例外処理が 必須 になる
- 業務アプリでは、例外メッセージをそのまま利用者に見せない配慮も重要
次章からは、いよいよ データベース を扱います。
まず第 16 章では、研修で使うデータベースの 土台づくり を行います。SQL Server Management Studio(SSMS)を使って、研修用データベース TrainingDB とテーブルを作成し、サンプルデータを投入します。
続く第 17 章で、C# のコンソールアプリから TrainingDB に接続し、この章で学んだ try-catch-finally と、新しく学ぶ using 文を組み合わせて、安全に DB を操作するパターンを身に付けます。
第 7 章から学んできたクラスやメソッド、そして本章の例外処理が、ここから一気に実用シーンで活きてきます。