C#で堅牢なアプリケーションを構築するうえで、例外処理は欠かせない要素です。なかでもtry-catch構文は、実行時の予期せぬエラーに対処するための基本の仕組みです。
しかし使い方を誤れば、障害の原因を隠してしまったり、パフォーマンスに悪影響を及ぼしたりすることもあります。
この記事では、C#におけるtry-catchの基本構文から適切な使い方を体系的に解説します。
目次
1.try-catch文とは?C#における基本構文と役割
C#においてtry-catch文は、例外(Exception)が発生する可能性のある処理を安全に実行するための基本構文です。エラーハンドリングの中核となるこの構文は、システムが不測の事態に遭遇した際の「防波堤」の役割を果たします。
try-catch文の基本構文
まずは、典型的なtry-catchの構文を確認しましょう。
try { // 例外が発生する可能性のある処理 } catch (Exception ex) { // 例外が発生した際の処理 } |
このようにtryブロックの中に実行したい処理を記述し、万が一例外が発生した場合はcatchブロックでそのエラーを補捉し、適切な処理を行います。catchでは、発生した例外を引数として受け取ることができ、その詳細情報をもとにログ出力やリトライ処理などが可能になります。
finallyブロックの役割
try-catch文には、finallyブロックを追加することもできます。
try { // 処理 } catch (Exception ex) { // エラー処理 } finally { // 必ず実行したい処理(リソースの開放など) } |
finallyブロックは、例外の発生有無にかかわらず、最後に必ず実行される処理を記述するためのセクションです。例えば、ファイルハンドルやDBコネクションのクローズ処理、メモリの解放など、後始末を担う重要な役割を持っています。
try-catchを使う理由
C#において例外が発生すると、それをキャッチしない限りプログラムは即座に停止します。つまり、try-catchがなければ、ユーザーにエラーメッセージが表示されたり、システムが異常終了したりする可能性が高くなります。
int number = int.Parse("abc"); // FormatExceptionが発生 |
このコードをそのまま実行すると例外が発生し、プログラムはクラッシュします。try-catchで囲うことで、例外を捕捉し、ユーザーに分かりやすいエラーメッセージを提示するなどの対応が可能になります。
try { int number = int.Parse("abc"); } catch (FormatException ex) { Console.WriteLine("数値の形式が正しくありません。"); } |
2.C# 例外の種類とcatchブロックの書き分け
C#で堅牢な例外処理を実装するには、まずどのような例外(Exception)が発生し得るのかを把握する必要があります。例外の種類ごとにcatchブロックを適切に分けることで、精度の高いエラー処理が実現できます。
ここでは代表的な例外の種類と、複数のcatchブロックを使った書き分け方、そしてすべてを一括でキャッチする場合の注意点について解説します。
C#でよく使われる例外の種類
以下は、C#で実務的によく遭遇する例外の一部です。
例外クラス | 概要 |
---|---|
NullReferenceException | null参照にアクセスしたときに発生 |
FormatException | 文字列を数値などに変換できない場合 |
IOException | ファイルやストリーム操作中の入出力エラー |
SqlException | データベース操作中に発生するエラー(System.Data.SqlClient) |
InvalidOperationException | 操作が無効な状態で呼び出された場合 |
ArgumentException | メソッドに渡された引数が不正な場合 |
これらの例外はすべてSystem.Exceptionの派生クラスであり、どれも独自の情報を持っています。単にExceptionですべてをキャッチするのではなく、可能な限り具体的な型でcatchすることが推奨されます。
複数のcatchブロックによる例外ごとの対応
複数のcatchブロックを使うことで、例外ごとに異なる処理を記述できます。C#では、より具体的な例外クラスを先に、汎用的なExceptionは最後に配置する必要があります。
try { // 例外が起こる可能性のある処理 } catch (FormatException ex) { Console.WriteLine("データ形式に誤りがあります。"); } catch (IOException ex) { Console.WriteLine("ファイル操作中にエラーが発生しました。"); } catch (Exception ex) { Console.WriteLine($"予期しないエラー: {ex.Message}"); } |
このように書くことで、想定される例外に対してきめ細やかな対応ができ、ユーザーへの通知やログ出力も状況に応じて適切に分けられます。
関連記事
【C#】Listと配列との違いや初期化・削除・要素数取得・型指定なしの使い方を紹介
【C#】DateTime完全ガイド:初期化・文字列変換から年月日・時間・ミリ秒操作まで詳細解説
3.C# エラー内容の取得とログ出力
例外をキャッチしたあとの処理として最も重要なのが、エラー内容の把握と記録(ログ出力)です。発生したエラーを正確に記録しておくことで、障害対応の迅速化や原因分析が可能になり、システムの信頼性を高めることにつながります。
本章では、catchブロック内で取得できる例外の詳細情報と、それを活かしたログ出力の設計について解説します。
例外情報の取得
C#のcatchブロックでは、Exception型のオブジェクトを使って、さまざまな詳細情報を取得できます。以下は代表的なプロパティです。
プロパティ | 内容 |
---|---|
Message | エラーメッセージ(ユーザー向け) |
StackTrace | エラー発生時のメソッド呼び出し履歴 |
InnerException | 入れ子になっている例外(原因の例外) |
Source | 例外が発生したアセンブリ名 |
TargetSite | 例外が発生したメソッド情報 |
例外情報の出力の例は下記の通りです。
catch (Exception ex) { Console.WriteLine("エラー内容: " + ex.Message); Console.WriteLine("スタックトレース: " + ex.StackTrace); if (ex.InnerException != null) { Console.WriteLine("内部例外: " + ex.InnerException.Message); } } |
これにより、どこで・なぜ・どのような形でエラーが起きたのかを明確にできます。
実務で使われるログ出力ライブラリ
C#では、標準のConsole.WriteLineに加え、実務では以下のようなログ出力ライブラリが広く使われています。
ライブラリ | 特長 |
---|---|
・構造化ログを簡単に出力可能 ・ファイル・DB・クラウド(Seq、Elasticsearch)など多彩な出力先に対応 | |
・柔軟な設定ファイル ・ローテーションや出力形式のカスタマイズが容易 |
関連記事
C#の正規表現|エスケープ処理や抽出、数字、match、ismatchなど実務で使える正規表現一覧
4.C# try-catchのthrowの使い方
try-catch構文で例外をキャッチした後、開発者は「例外をここで処理しきるのか、それとも上位に渡すのか」という判断を迫られます。このとき活用されるのがthrowです。本章では、throwの正しい使い方とthrow exとの違いについて、実務の観点から詳しく解説します。
throwの基本
C#におけるthrowには、主に以下の2つの使い方があります。
新しい例外のスロー
例外を新たに作成してスローする方法です。
throw new InvalidOperationException("操作が無効です"); |
この形式は、特定の条件を検出した際、開発者が意図的に例外を投げたい場合に使われます。
既存の例外を再スローする
catchでキャッチした例外を、そのまま上位に投げ直す(再スロー)方法です。
catch (Exception ex) { // ログ出力などを行ってから throw; // 元の例外をそのまま再スロー } |
このように、例外をログ出力などで一時的に処理しつつ、最終的な対応は上位レイヤーに任せたいときに再スローを使います。
throwとthrow exの違いに注意
初心者がよくやってしまうミスのひとつが、次のような書き方です。
catch (Exception ex) { throw ex; // NG例 } |
この書き方はスタックトレース(例外が発生した正確な箇所の情報)を初期化してしまうという問題があります。再スローされた例外の発生元がこのcatchブロックになってしまい、本来のエラー発生箇所を特定できなくなります。
正しい再スローの方法は下記です。
catch (Exception ex) { LogError(ex); throw; // スタックトレースを保持したまま再スロー } |
例外の再スローが必要なケース
例外処理では、すべてをその場で処理してしまうのではなく、以下のように上位へ渡す設計が効果的な場合もあります。
エラーの発生箇所ではログのみ記録し、ユーザーへの通知は上位レイヤーで制御したい
非同期処理やAPI層で統一的にエラーハンドリングしたい
フレームワークや外部システムに例外処理を委ねる設計方針にしている
このような場合、再スローは非常に有効です。ただし、再スローする際は必ず元の例外情報を損なわない形で行うよう注意しましょう。
5.C# try-catchの使いどころと避けるべきケース
try-catchはC#における強力な例外処理構文ですが、どんな場所にも闇雲に使えばよいというものではありません。適切なタイミングとスコープで使わなければ、逆に可読性やパフォーマンスを損なう要因にもなり得ます。
本章ではtry-catchを積極的に使うべきケースと、むしろ避けるべきケースについて、実務目線で解説していきます。
try-catchが有効に機能する使いどころ
try-catch構文は例外処理の基本ですが、どんな場面でも一律に使うのではなく「本当に必要な場所」に絞って活用することで、アプリケーションの堅牢性とパフォーマンスの両立が可能になります。
外部要因に依存する処理
例えばファイルアクセス・ネットワーク通信・データベース接続などは、外部環境の影響を受けやすく、予期せぬ例外が発生しやすい処理です。こうした場面では、try-catchによってアプリケーション全体の停止を防ぎ、適切なエラーメッセージやリトライ処理を組み込むことができます。
try { using (var reader = new StreamReader("config.txt")) { string config = reader.ReadToEnd(); } } catch (IOException ex) { Console.WriteLine("ファイル読み込みエラー: " + ex.Message); } |
ユーザー入力を受け取る処理
ユーザーの入力値が不正な形式であることはよくあります。文字列の数値変換や日付解析などでは、フォーマットエラーが頻出します。
try { int age = int.Parse(userInput); } catch (FormatException) { Console.WriteLine("数値として認識できません。"); } |
try-catchを避けるべきケース
一方でtry-catchの使いすぎは、かえってコードの品質やパフォーマンスを損なう原因になります。例外処理は高コストな処理であり、「通常の処理フローの一部」として用いるのは本来の目的とは異なります。
コントロールフローとして使う
例外は「本当に例外的な事態」のための機能です。通常の分岐処理や判定の代わりに使うのはNGです。TryParseのような安全な変換メソッドがある場合は、そちらを優先的に使いましょう。
if (int.TryParse("abc", out int result)) { // 成功時の処理 } else { result = 0; } |
高頻度で実行される小さな処理
ループ内などで高頻度に呼ばれる処理にtry-catchを入れると、パフォーマンスが大幅に低下します。C#において例外処理はコストが高いため、発生頻度が高くなる場所には極力置かないのが原則です。
関連記事
C# foreach完全ガイド|基本構文からコレクション操作・LINQ連携・ループ制御まで徹底解説
6.C# 全ての例外をcatchする設計について
開発現場でよく議論になるのが、「すべての例外をcatch (Exception)でまとめて処理するべきか否か」という問題です。一見すると便利に思えるこの方法ですが、設計次第ではかえって危険なコードになりかねません。
本章では、全例外補足の是非を多角的に検討し、現場での正しい判断基準を示します。
catch (Exception) による包括的な例外処理のメリット
catch(Exception)を使う最大のメリットは、あらゆる例外を取りこぼさずにキャッチできるという点です。例えば、リリース済みのアプリケーションで「何が起きているのかわからない」ケースに対して、ログを残す目的で全例外を捕まえるのは有効です。
try { // 複数の潜在的なエラーが起こりうる処理 } catch (Exception ex) { Log.Error(ex, "予期せぬエラーが発生しました。"); throw; // 上位に再スローする場合も } |
すべての例外をcatchすることのリスク
一方で、全ての例外をキャッチする設計には、次のようなリスクが潜んでいます。
例外の意図が不明確になる
どんな種類のエラーが起きたかを判別しないまま一括処理することで、原因の特定や再発防止策が曖昧になります。特に、ビジネスロジックの中でこれを多用すると、処理の意図が不透明になります。
致命的な例外を握りつぶす可能性がある
スタックオーバーフローやヒープ破損、セキュリティ関連のクリティカルな例外まで補足してしまい、本来システムを停止すべき状態でも動作を継続してしまう恐れがあります。
バグを発見しづらくなる
開発中やテスト環境で全例外を握りつぶすコードがあると、本来失敗すべきタイミングでエラーが検出されず、リリース後に問題が発覚するといったリスクが高まります。
7.まとめ
C#のtry-catch構文は、例外発生時にアプリケーションの安全性と信頼性を守る仕組みです。しかし、使い方を誤ればバグの温床や調査困難なトラブルの原因にもなり得ます。
本記事では、基本構文から使いどころ・再スローの注意点・ログ出力・避けるべき設計までを体系的に解説しました。実務で活きる例外処理力は、習得すればするほどコードの堅牢性を高める武器になります。
本記事が皆様にとって少しでもお役に立てますと幸いです。
「フリーランスボード」は、数多くのフリーランスエージェントが掲載するITフリーランスエンジニア・ITフリーランス向けの案件・求人を一括検索できるサイトです。
開発環境、職種、単価、稼働形態、稼働日数など様々な条件から、あなたに最適なフリーランス案件・求人を簡単に見つけることができます。
単価アップを目指す方や、自分の得意なスキルを活かせる案件に参画したい方は、ぜひ「フリーランスボード」をご利用ください。