C#と諸々

C#がメインで他もまぁ諸々なブログです
おかしなこと書いてたら指摘してくれると嬉しいです(´・∀・`)
つーかコメント欲しい(´・ω・`)

2007/07/28 20:25
PowerShell の例外処理は VB6 のような非構造化例外処理を採用している。
PowerShell では trap ブロックによって例外を捕捉する。
この例外処理の動作がけっこうややこしい ( 特に $ErrorActionPreference が関わってくる条件 ) ので、検証してまとめてみた。 ( 掲載したサンプルコードはそのまま実行可能。 )


[ throw ステートメント ]
PowerShell では、 throw ステートメントを使用して例外を明示的にスローすることができる。スローできるのは Exception 派生クラスのインスタンス、ErrorRecord クラス (System.Management.Automation) のインスタンス、文字列である。これ以外のオブジェクトをスローしようとした場合、ToString メソッドによって文字列に変換されてスローされる。
なお、文字列がスローされた場合は、その文字列を元に RuntimeException クラス (System.Management.Automation) のインスタンスが生成され、この RuntimeException がスローされる。


[ trap ブロック ]
trap ステートメントの直後に例外の型を指定しておくと、その型の例外のみハンドルされる。(型指定は省略可。)
trap ブロックは一つのスコープに対して複数用意することができるが、実際に有効になるのはその中の一つだけであり、先に定義された trap ブロックが使用される。trap ブロックはスコープ内のどこに配置してもよく、同一スコープ内の全ての処理に作用する。
例えば、以下のコードでは、どこで例外が発生しても、"aaa" と出力される。

function Sample1
{
  param ([Int32] $arg0)
 
  if ($arg0 -eq 1) { throw "いち"; }
  trap { "aaa"; }
  if ($arg0 -eq 2) { throw "に"; }
  trap { "bbb"; }
  if ($arg0 -eq 3) { throw "さん"; }
  trap { "ccc"; }
}

Sample1 1;
Sample1 2;
Sample1 3;



[ 捕捉した例外を取得 ]
trap ブロック内では &_ 変数にて、捕捉した例外にアクセスできる。ただし、$_ 変数に格納されているのは ErrorRecord オブジェクトであり、実際に発生した例外は ErrorRecord の Exception プロパティから取得する。つまり、 $_.Exception にて実際に発生した例外を取得できる。
例外を捕捉して新しく別の例外としてスローする例を以下に示す。

function Sample2
{
  throw (New-Object "System.ArgumentException");
  trap [System.ArgumentException]
  {
    $ex = $_.Exception;
    throw (New-Object "System.IO.FileNotFoundException" @("ファイルが見つからないよ", $ex));
  }
}

Sample2;



[ 例外の処理方法 ]
例外が発生すると、その例外を捕捉できる trap ブロックが同一スコープ内に存在するかどうか探される。存在した場合は、その trap ブロックに制御が移り、存在しない場合は上位スコープに例外がスローされる。
例外が上位スコープにスローされると、上位スコープで再び trap ブロックが存在するかどうか探される。これを繰り返し、最上位のスコープまで trap ブロックが存在しなかった場合、処理は中断され、例外がユーザーに通知される。


[ tarp ブロック用のステートメント ]
trap ブロック内では、以下の2つのステートメントを使用できる。

break ステートメント
スコープを抜ける。例外は上位に再スローする。

continue ステートメント
例外が発生したコードの次のコードに制御を移す。例外は握りつぶされる。


[ continue ステートメントについて ]
continue ステートメントが実行されると例外が発生したコードの次のコードに制御が移動すると書いたが、厳密には、「同一スコープ内での次のコードに制御を移動」となる。
以下のようなコードを例に挙げてみる。

function Sample3
{
  &{
    "ほげ";
    throw "throw1";
    "Hoge";
  };
  &{
    "ふが";
    throw "throw2";
    "Fuga";
  };
  trap
  {
    continue;
  }
}

Sample3


この Sample3 関数内には、trap ブロックを除いて2つのブロックが存在し、この2つのブロックの中には trap ブロックが含まれていない。
このコードを実行すると、まず1つ目のブロックで "ほげ" と出力した後例外が発生する。一つ目のブロック内には trap ブロックが存在しないので、例外は上位にスローされる。その結果、一つ上位のスコープにある trap ブロックによって例外がハンドルされる。trap ブロック内には continue ステートメントが記述されているので、処理を続行することになる。遷移先は「同一スコープ内での次のコード」なので、1つ目のブロック内の処理は中断され、2つ目のブロックに制御が移る。2つ目のブロックでも同様の動作が繰り返される。
結果として、以下の出力が得られる。

ほげ
ふが


[ $ErrorActionPreference 変数 ]
trap ブロック内で break ステートメントも continue ステートメントも実行されなかった場合、$ErrorActionPreference 変数の設定に従った動作が行われる。
ただし、trap ブロック内で新たに例外が発生した場合は除く。 ( 例えば捕捉した例外を元に新しく例外を生成してスローした場合等。 )
また、捕捉できる trap ブロックがどこにもない例外が発生した場合 ( 2007/11/13 追記:throw ステートメントでスローした例外は除く ) も、$ErrorActionPreference 変数の設定に従った動作が行われるが、一度でも trap ブロックに捕捉された例外に対しては動作しない。

$ErrorActionPreference 変数には ActionPreference 列挙体 ( System.Management.Automation ) の列挙値を設定できる。ActionPreference 列挙体には以下の4つの列挙値が用意されている。

SilentlyContinue ( サイレント続行 )
例外が発生したコードの次のコードに制御を移す。例外は握りつぶされる。
trap ブロック内で continue ステートメントが実行された場合と同様の挙動。

Stop ( 中断 )
スコープを抜ける。例外は上位に再スローする。
trap ブロック内で break ステートメントが実行された場合と同様の挙動。

Continue ( 続行 )
例外が発生したコードの次のコードに制御を移す。例外はユーザーに通知される。( 再スローされるわけではない。 )
この値が既定の設定。

Inquire ( 問い合わせ )
例外のメッセージを表示し、動作をユーザーに問い合わせる。選択できる動作は、続行, サイレント続行, 中断, 中断 ( 例外握りつぶし ) の4つ。


・trap ブロック内で break ステートメントも continue ステートメントも実行されなかった場合の例
trap ブロック内で break ステートメントも continue ステートメントも実行されなかった場合の例を以下に示す。

function Sample4
{
  &{
    throw (New-Object "System.InvalidOperationException" @());
    "ほげ";
    trap
    {
      "trap1";
    }
  };
  "ふが";
  trap
  {
    "trap2";
    break;
  }
}

$ErrorActionPreference = "SilentlyContinue";
Sample4;


このコードを実行すると、以下の出力が得られる。

trap1
ほげ
ふが

Sample4 関数内のスクリプトブロックは一行目で例外をスローしている。この例外を捕捉する trap ブロックが同一スコープ内にあるので、この trap ブロックに制御が移動する。しかし、break ステートメントも continue ステートメントも記述されていないので、trap ブロックを抜けた後は $ErrorActionPreference 変数に従った動作が行われる。
$ErrorActionPreference 変数には "SilentlyContinue" を設定しているので、例外は握りつぶされ、次の行の "ほげ" という文字列が出力され、続いて "ふが" という文字列が出力される。

"trap2" は出力されないことに注意する。trap ブロック内で break 及び continue が実行されなかった場合、その上位に trap ブロックがあろうがなかろうが、$ErrorActionPreference 変数に従った動作が行われる。その結果例外が握りつぶされたため、2つ目の trap ブロックは実行されていない。


・捕捉できる trap ブロックがどこにもない例外が発生した場合の例
捕捉できる trap ブロックがどこにもない例外が発生した場合の例を以下に示す。

function Sample5
{
  &{
    throw (New-Object "System.InvalidOperationException" @());
    "ほげ";
  };
  "ふが";
}

$ErrorActionPreference = "SilentlyContinue";
Sample5;


このコードを実行すると、以下の出力が得られる。

ほげ
ふが

Sample5 関数内のスクリプトブロックは一行目で例外をスローしている。この例外を捕捉する trap ブロックが、同一スコープ内にも上位スコープ内にも存在しないため、$ErrorActionPreference 変数に従った動作が行われる。
$ErrorActionPreference 変数には "SilentlyContinue" を設定しているので、例外は握りつぶされ、次の行の "ほげ" という文字列が出力され、続いて "ふが" という文字列が出力される。


[ テクニック ]
適当に考えた、テクニックというほどでもないテクニックを3つほど。
「もっとエレガントに書けるだろ!」って方は是非コメントでお教えください m( _ _ )m

try もどき
trap ブロックは配置されているスコープ内の全てに対して有効である。C# の try ブロック のように範囲を限定する特別な機能は備わっていない。
C# の try ブロック のように範囲を限定したい場合は以下のようなテクニックが使える。

function Technique1
{
  param ([int] $arg0)
  &{
    if ($arg0 -eq 1)
    {
      throw (New-Object "System.ArgumentException" @());
    }
    trap [System.ArgumentException]
    {
      "1つ目のブロックで発生した例外を捕捉";
      break;
    }
  };

  &{
    if ($arg0 -eq 2)
    {
      throw (New-Object "System.ArgumentException" @());
    }
    trap [System.ArgumentException]
    {
      "2つ目のブロックで発生した例外を捕捉";
      break;
    }
  };
}

Technique1 1;
Technique1 2;


つまり、trap ブロックが有効となる範囲を狭めるために、スクリプトブロックを作成し、& を使ってその場で実行させるということ。


catch ( 例外握りつぶし ) もどき
PowerShell では trap ブロックが C# の catch ブロックに相当するが、catch ブロックで例外を握りつぶすような機能が用意されていない。 ( continue ステートメントを使えば例外を握りつぶしせるが、そのまま次のコードに復帰してしまう。 )
C# の catch ブロックで例外を握りつぶすような感じの処理を行いたい場合は以下のようなテクニックが使える。

function Technique2
{
  &{
    &{
      "この次に例外が発生";
      throw "hoge";
      "例外は握りつぶされるが、ここは出力されない";
    };
    trap
    {
      continue;
    }
  };
  "例外が握りつぶされ、このコードが実行される";
}

Technique2


つまり、trap ブロックが置かれているスコープ内に、スクリプトブロックを一つだけ配置し、continue ステートメントによって例外を握りつぶす。こうすることによって、trap ブロックが一つ上位に配置されているため、スクリプトブロックの中の処理は中断される。


finally もどき
PowerShell には C# の finally ブロックのように確実な後処理を行うための特別な機能が用意されていない。
C# の finally ブロック のように範囲を限定したい場合は以下のようなテクニックが使える。

function Technique3
{
  param ([Boolean] $arg0)
 
  $finallyBlock =
    {
      "確実な後処理";
    };

  if ($arg0)
  {
    throw "すろー";
  }
  &$finallyBlock;

  trap
  {
    &$finallyBlock;
    break;
  }
}

Technique3 $False;
Technique3 $True;


つまり、確実な後処理をスクリプトブロックとして変数に格納しておき、正常終了時も、例外捕捉時もそれを呼び出すということ。

try - catch - finally もどき
これらを状況に応じてうまく組み合わせれば try - catch -finally が完全に再現できる。 ( でも可読性はあまりよくない。。。 )
まぁ、「catch ( 例外握りつぶし ) もどき」はあまり使うことないかな。


# 追記 ( 2007/11/07 )

C# の try -catch - finally に近い形式で記述する方法を記事にしました。

# 追記ここまで


[ 最後に ]
PowerShell の例外処理はこんなところかな。
ん、$Error 変数?なんのことやら。


# 追記 ( 2007/11/14 )

例外処理には、まだ他にも書くべきことがありました。 → エラーパイプライン

# 追記ここまで











トラックバックURL↓
http://csharper.blog57.fc2.com/tb.php/149-28de79a0