ある処理でプログラムで例外が発生して、その例外がどういった例外が知ることができても
その例外が起きないようにする必要性があります。
原因調査を行うときにその処理がプログラムの中のいろいろなところから呼ばれていている処理の場合
「この例外はどこから呼ばれて発生したのか」まで知ることができると原因究明の手助けになります。
この呼び出しの追跡をデバッグで「スタックトレース(スタックの追跡)」と呼ばれています。
スタックトレースに必要なもの
スタックトレースを行うプログラムのデバッグ情報(Visual Studioでは’pdb:プログラムデータベース’と呼ばれる情報です)
デバッガはこの情報を使って、プログラムの処理で呼び出されたメソッドの名前などを取り扱います。
下記のようなプログラムでスタックトレースを行ってみます。
class Program { static void CallException() { throw new Exception("ここで例外を発生させる"); } static void FuncA() { CallException(); } static void FuncB() { FuncA(); } static void FuncC() { FuncB(); } static void Main(string[] args) { try { FuncC(); } catch (Exception ex) { Console.WriteLine("例外メッセージ --> " + ex.Message); Console.WriteLine("スタックトレース "); Console.WriteLine(ex.StackTrace); } } }
例外が発生したところでは下記のようなメッセージが表示されます。
例外メッセージ --> ここで例外を発生させる スタックトレース 場所 StackTraceTest.Program.CallException() 場所 D:\work\test\StackTraceTest\StackTraceTest\Program.cs:行 ** 場所 StackTraceTest.Program.FuncA() 場所 D:\work\test\StackTraceTest\StackTraceTest\Program.cs:行 ** 場所 StackTraceTest.Program.FuncB() 場所 D:\work\test\StackTraceTest\StackTraceTest\Program.cs:行 ** 場所 StackTraceTest.Program.FuncC() 場所 D:\work\test\StackTraceTest\StackTraceTest\Program.cs:行 ** 場所 StackTraceTest.Program.Main(String[] args) 場所 D:\work\test\StackTraceTest\StackTraceTest\Program.cs:行 ** (**は実際のソースコードの行番号です)
実際に例外が発生したメソッドが一番上で、一番下が呼び出し元になります。
ここで登場したStackTraceプロパティですが、このプロパティはstring型です。
上記のサンプルだと、おおよそ呼び出した順番はわかりますが、下記のような場合はコードだけでは分かりません。
class BaseClass { public BaseClass() { // 生成するだけ } public virtual void Exec(int param) { if(param < 0) { Program.CallException(); } } } class ClassA : BaseClass { public ClassA() : base() { // 生成するだけ } public override void Exec(int param) { // オーバーライドして-1して基底クラスを呼ぶ base.Exec(param - 1); } } class ClassB : BaseClass { public ClassB() : base() { // 生成するだけ } public override void Exec(int param) { // オーバーライドして-2して基底クラスを呼ぶ base.Exec(param - 2); } } class Program { public static void CallException() { throw new Exception("ここで例外を発生させる"); } static void Main(string[] args) { try { int exec_param = -1; BaseClass obj = null; if (args[0] == "1") { obj = (BaseClass)new ClassA(); } else if (args[0] == "2") { obj = (BaseClass)new ClassB(); } else { throw new Exception("ここで例外を発生させる"); } if(!int.TryParse(args[1], out exec_param)) { throw new FormatException(string.Format("parse string is not numeric {0}", args[1])); } obj.Exec(exec_param); } catch (Exception ex) { Console.WriteLine("例外メッセージ --> " + ex.Message); Console.WriteLine("スタックトレース "); Console.WriteLine(ex.StackTrace); } } }
実際にはプログラム起動時に渡す引数の値で例外が発生するのですが、例外発生時に下記のようなメッセージが表示されました。
例外メッセージ --> ここで例外を発生させる スタックトレース 場所 StackTraceTest.Program.CallException() 場所 D:\work\test\StackTraceTest\StackTraceTest\Program.cs:行 ** 場所 StackTraceTest.BaseClass.Exec(Int32 param) 場所 D:\work\test\StackTraceTest\StackTraceTest\Program.cs:行 ** 場所 StackTraceTest.ClassB.Exec(Int32 param) 場所 D:\work\test\StackTraceTest\StackTraceTest\Program.cs:行 ** 場所 StackTraceTest.Program.Main(String[] args) 場所 D:\work\test\StackTraceTest\StackTraceTest\Program.cs:行 ** (**は実際のソースコードの行番号です)
このスタックトレースのメッセージからわかること
このコードと例外メッセージから
- オブジェクト’obj’の生成されたクラスはClassBだった —> 第1引数は”2″であった。
- 例外が発生する条件は、BaseClass.Execの仮引数’param’が負の値のときに発生する。
ClassB.ExecはBaseClass.Execを呼び出すときに2引く実装になっている —> 第2引数は「2を引くと負の値になる数値文字」だった。
ということが分かります。
ということは、このプログラムは起動時の第2引数の値を2以上の値にすれば例外が発生しなくなります。
スタックトレースを使うとこのように、例外が起きたときの状態を知る手助けになります。