今回の記事では例外処理について解説していきます。
例外処理はアプリ実行時に予想していない自体が発生した時に使用するC#の機能になります。
例外処理をマスターしてばっちりエラーハンドリングしていきましょう。
前回の記事:
例外処理とは
例外処理とはアプリ実行中に想定していないデータが現れたり、指定したファイルが存在しない時など想定外の処理が実行された時に使用できるC#の機能になります。
例えば参照型にnullが入っているときにメンバへアクセスした時や、配列の要素数以上の添字を指定したときに発生します。
1 2 3 4 5 |
string str = null; var l = str.Length; // <- 例外発生 int[] arr = new int[3]; arr[10] = 10; // <- 例外発生 |
例外の投げ方(throwキーワード)
C#やUnityの機能を間違って使用した時には例外が発生します。また、throwキーワードを使用することでこちらから任意の例外を発生(※)させることができます。
発生させる例外は必ずSystem.Exception型を継承したものにしてください。
(※例外を発生させることを例外を投げると言います。)
- 例外の投げ方:
throw <投げる例外の型>;
1 |
throw new System.Exception("例外発生"); |
例外が発生しますとアプリはその時点で処理を終了しますので、基本的に例外が発生しないようにプログラムを作成していきましょう!
発生した例外の対処法について(try-catch文)
例外が発生した時はtry-catch文を書くことでその例外を検知、対処できます。
try-catch
文の書き方は例外が発生するかもしれない場所に次のようにブロックで括ってください。
- try-catch文の書き方:
try { <例外が発生するかもしれない処理> } catch (<発生する例外の型> <変数名>) { ... }
1 2 3 4 5 6 |
try { string str = null; var n = str.Length; // <- 例外発生 } catch(System.NullReferenceException e) { //例外が発生した時の処理を書く } |
一つのtry
文にcatch
文は二つ以上書くことができます。例外が発生した場合、基本的には上に書いたcatch文が優先的に実行されます。
しかし、型が合わない場合は投げられた例外と型変換が可能な型を指定したcatch文の処理が実行されます。
1 2 3 4 5 6 7 8 9 10 11 12 |
try { int[] arr = new int[3]; arr[10] = 10; // <- 例外発生(IndexOutOfRangeException) string str = null; var n = str.Length; //<- 例外発生(NullReferenceException) } catch (System.NullReferenceException e) { //nullアクセスが発生した時の対処処理 } catch (System.IndexOutOfRangeException e) { //添字アクセス違反が発生した時の対処処理 //このサンプルコードではこちらが実行される。 } |
もしcatch文と一致しない例外が投げられた時はその例外は無視されます。この時の処理は次の項目で説明します。
対処できなかった例外について
try-catch
文がないなど、もし発生した例外を対処できなかった場合はシステム側が次の手順をとります。
例外が発生した時の処理の流れ
- try-catch文の中で発生したら、その中で対処する。
- もしtry-catch文と一致していない例外やtry-catch文の外で発生したなら、メソッドを呼び出したメソッドの中を探す。
- 1に戻る。もし呼び出しメソッドがなければそこでアプリを終了する。
System.Exceptionをcatch文で用いると全ての例外に対応できるので、例外を必ず対処したい時に利用してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class A { void MethodA() { MethodB(); } void MethodB() { MethodC(); } void MethodC() { throw new System.Exception("例外発生"); } } try { var inst = new A(); //MethodAを呼び出すとMethodCの中で例外が発生する。 // その時のメソッドのコールスタックは次のものになる。 // (ルートメソッド) -> MethodA -> MethodB -> MethodC inst.MethodA(); } catch(System.Exception e) { //MethodCで投げられた例外はここで処理される } |
メソッドコールスタックについて
メソッドを呼び出した順はアプリによって記録されています。このことをメソッドコールスタック(Method Call Stack)などと呼んだりします。
コールスタックにはサイズ上限があります。もし上限を超えてしまった場合でも例外(System.StackOverflowException)が発生しますので覚えておくといいでしょう。
catch文の中のthrowキーワードについて
catch文の中でもthrowキーワードを書くことができます。その場合はcatch文に渡された例外をこのメソッドの呼び出し元へ投げます。
1 2 3 4 5 |
try { throw new System.Exception("例外発生"); } catch(System.Exception e) { throw; // <- これより先に呼び出されたメソッドに発生した例外を再び投げる。 } |
finally文について
try-catch文の最後にfinally文を書くことで例外が発生した時に共通した後処理を書くことができます。
1 2 3 4 5 6 |
try { throw new System.Exception("例外発生"); } catch (System.Exception e) { } finally { //例外が発生した時の共通した後処理をここに書く } |
例外の自作の仕方について
C#やUnityではあらかじめいくつかの例外型が提供されています。これらの例外型はどれもSystem.Exceptionを継承しています。
- IndexOutOfRangeException:添字アクセスエラー
- NullReferenceException:nullアクセスエラー
- FileNotFoundException:ファイルが存在しないというエラー
- ArgumentException:メソッドの引数が原因のエラー
- DivideByZeroException:0除算エラー
- InvalidCastException:型変換に失敗した時のエラー
- StackOverflowException:メソッドコールスタックの上限を超えた時のエラー
などなど。
上にあげた以外の例外ももちろん自作できます。自作する際は必ずSystem.Exceptionを継承してください。
1 2 3 4 5 6 7 8 9 |
class MyException : System.Exception { public MyException(string message) : base (message){ } } try { throw new MyException("例外発生"); } catch (MyException e) { //例外の対処 } |
【実践】Unityで例外を体験してみよう!
それでは実際にUnityで例外を体験してみましょう!
Unityで例外が発生した時は発生源のコンポーネントの処理はその時点で中断されますが、アプリ全体の処理は止まりません。
そのため次の画像のように同じエラーが大量に発生します。(これまで、アプリでエラーを起こしたことがある方は既にみたことがあるかもしれません。)
今回のサンプルでは例外が起きてもエラーを出さないようにしてみましょう!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
using UnityEngine; public class Sample : MonoBehaviour { public Color Color; public MeshRenderer Mesh; void Start() { try { Mesh.material.color = Color; } catch (System.Exception e) { Debug.LogWarning($"Meshをアタッチしてください。 : {e}"); //例外が発生したら警告メッセージを表示している。 } } void Update() { try { var color = Mesh.material.color; color.r *= 1.01f; Mesh.material.color = color; } catch (System.Exception) { //例外が発生してもエラーを起こさないようにしている。 //何もしない時は変数名を省略できる。 } } } |
今回のサンプルコードではMeshフィールドの色を適当に変更するものになっています。これまでのサンプルではMeshのNullチェックを行っていましたが、今回はそれを省略しています。
そのためMeshフィールドをアタッチしていない場合は例外が発生しますが、try-catch文の中でその対処を行っているためアプリを安全に実行することができるようになっています。
catch文の中では例外が発生した時の処理を書きますが、基本的にログを残したり、開いているファイルを閉じたりと必要最低限の処理だけを行いましょう!
まとめ
今回の記事では例外処理について解説してきました。簡単にまとめます。
- 例外処理は想定外なことが起きた時に使用するC#の機能
- 例外を投げたい時はthrowキーワードを使用する。
- 発生した例外に対処するには例外が発生するであろう場所をtry catch文で囲む。
- try文の中で発生した例外はその後のcatch文で処理できる。
- catch文は発生する例外に応じて複数書くことができる。
- catch文に指定した例外は型変換ができるならキャッチできる。
- catch文で処理できなかった例外はメソッドを呼び出した側のメソッドで再度確認する。
- もし例外が処理できなかったら、その時点でアプリの実行を終了する。
- 全ての例外クラスはSystem.Exceptionを継承することを推奨している。
- Unityでは例外が発生してもアプリ自体の動作は中断しない。代わりに発生したコンポーネントの処理がそこで中断される。
ここまででUnityC#講座入門編は終了です。お疲れ様でした。
まだ理解が不十分なところや難しかったところなどは適宜復習してみてください。
文法の基本を抑えたので次は実際にゲーム開発講座などをこなしていきましょう。
コメント