構文:条件を指定して例外をキャッチするには?[C# 6/VB]:.NET TIPS
C# 6/VBでは例外処理時に、catch句にwhen句を付加して、例外をキャッチする条件を指定する方法を解説。また、その応用例も紹介する。
対象:C# 6.0(Visual Studio 2015)以降、VB 7.0(Visual Studio .NET 2002)以降
C#でプログラミングしていて、例外をキャッチするときに条件を付けたいと思ったことはないだろうか? 例えば、渡した引数が原因となって例外を引き起こしたときに、特定の引数の場合だけ処理を変えたいといった場合だ。これまでのC#では、取りあえずキャッチしてから、catchブロックの中で例外を検査して処理を分岐させるしかなかった。
ところでVisual Basic .NET(以降、VB)には、当初から条件付きで例外をキャッチする機能があった。その機能が、ついにC# 6.0にも導入されたのである。本稿では、その使い方を解説しよう。
catch句にwhen句を付ける
特定の条件のときだけcatchブロックの中身を実行させるには、catch句の後ろにwhen句を付ける。その例を次のコードに示す。
static void CatchSample(string num1, string num2)
{
try
{
// ここで引数num1/num2を使う処理を行う
// 処理できないときはArgumentExceptionが発生するものとする
}
catch (ArgumentException ex) when (ex.ParamName == "num1")
{
// ここは、引数num1が例外を引き起こしたときだけ実行される
Console.WriteLine($"■引数num1(値は{num1})によって例外が発生");
}
catch (ArgumentException ex)
{
// ここは、num1以外の引数(=引数num2)が例外を引き起こしたときだけ実行される
Console.WriteLine($"■num1以外の引数(値は{num2})によって例外が発生");
}
}
Sub CatchSample(num1 As String, num2 As String)
Try
' ここで引数num1/num2を使う処理を行う
' 処理できないときはArgumentExceptionが発生するものとする
Catch ex As ArgumentException When ex.ParamName = "num1"
' ここは、引数num1が例外を引き起こしたときだけ実行される
Console.WriteLine($"■引数num1(値は{num1})によって例外が発生")
Catch ex As ArgumentException
' ここは、num1以外の引数(=引数num2)が例外を引き起こしたときだけ実行される
Console.WriteLine($"■num1以外の引数(値は{num2})によって例外が発生")
End Try
End Sub
when句の書き方はC#とVBでほぼ同じだ。C#のwhen句では、条件式をかっこでくくらなければならない。
上の例では、ArgumentExceptionをキャッチするcatchブロックが2つある。最初のcatchブロックにはwhen句が付いており、その条件を満たすときだけ最初のcatchブロックの中身が実行される(このときは、2番目のcatchブロックの中身は実行されない)。
ArgumentExceptionが発生しても、最初のcatchブロックのwhen句の条件を満たさないときは、2番目のcatchブロックの中身が実行される(このときは、最初のcatchブロックの中身は実行されない)。
応用:常にfalseを返すwhen句
when句の条件式が常にfalseを返す場合、そのcatchブロックの中身は実行されず、その下のcatchブロックへと処理が流れていく。ちょっとトリッキーではあるが、それを応用して全ての例外をロギングすることもできる。
発生した全ての例外をログに書き出そうとしたら、どうすればよいだろうか? 全てのcatchブロックにロギングするコードを書くか、あるいは、全ての例外をキャッチしておいて、ロギングしてから例外を検査して処理を分岐させるかだろう。
ところが、when句を応用すると、そのような処理が簡潔に書ける。まず、常にfalseを返すロギング用のメソッドを作っておく。そして、try句の直後に全ての例外をトラップするcatch句を置き、そこにwhen句を付けて、そのロギング用のメソッドを与えるのだ。こうすると、ロギング用のメソッドは実行されるが、そのcatch句の中身は実行されず、その後に続くcatch句に処理が移っていくのである。
具体的なサンプルコードを次に示そう。コンソールアプリである(先に示したサンプルコードの具体例にもなっている)。実際に出力された結果を、コメントとして「⇒」記号に続けて掲載してある。ちょっと長いコードだが、どのようにして出力された結果なのかを、じっくり読み解いてみてほしい。
using System;
using static System.Console;
class Program
{
// 例外が発生するメソッド
static int SampleMethod(string num1, string num2)
{
int n1, n2;
if (!int.TryParse(num1, out n1))
throw new ArgumentException("整数に変換できない文字列", nameof(num1));
if (!int.TryParse(num2, out n2))
throw new ArgumentException("整数に変換できない文字列", nameof(num2));
checked
{
return n1 * n2;
}
}
// ログに書き出すメソッド(ここではサンプルとしてコンソールに出力)
static bool DummyLog(Exception ex)
{
WriteLine($"DummyLog: {ex.Message}");
return false; // 常にfalseを返す
}
// when句を使う例
static void CatchSample(string num1, string num2)
{
try
{
var result = SampleMethod(num1, num2);
WriteLine($"{num1} * {num2} = {result}");
}
catch (Exception ex) when (DummyLog(ex))
{
// DummyLogメソッドは常にfalseを返すので、
// このcatchブロック内は実行されない
}
catch (ArgumentException ex) when (ex.ParamName == "num1")
{
WriteLine($"■引数num1(値は{num1})によって例外が発生");
}
catch (ArgumentException ex)
{
WriteLine($"■num1以外の引数(値は{num2})によって例外が発生");
}
catch (Exception ex)
{
WriteLine($"■例外が発生:{ex.Message}");
}
}
static void Main(string[] args)
{
CatchSample("2", "3");
// ⇒ 2 * 3 = 6
CatchSample("a", "3");
// ⇒ DummyLog: 整数に変換できない文字列
// パラメーター名:num1
// ■引数num1(値はa)によって例外が発生
CatchSample("2", "BB");
// ⇒ DummyLog: 整数に変換できない文字列
// パラメーター名:num2
// ■num1以外の引数(値はBB)によって例外が発生
CatchSample("123456789", "123");
// ⇒ DummyLog: 算術演算の結果オーバーフローが発生しました。
// ■例外が発生:算術演算の結果オーバーフローが発生しました。
#if DEBUG
ReadKey();
#endif
}
}
Imports System.Console
Module Module1
' 例外が発生するメソッド
Function SampleMethod(num1 As String, num2 As String) As Integer
Dim n1, n2 As Integer
If (Not Integer.TryParse(num1, n1)) Then
Throw New ArgumentException("整数に変換できない文字列", NameOf(num1))
End If
If (Not Integer.TryParse(num2, n2)) Then
Throw New ArgumentException("整数に変換できない文字列", NameOf(num2))
End If
Return n1 * n2
End Function
' ログに書き出すメソッド(ここではサンプルとしてコンソールに出力)
Function DummyLog(ex As Exception) As Boolean
WriteLine($"DummyLog: {ex.Message}")
Return False ' 常にFalseを返す
End Function
' when句を使う例
Sub CatchSample(num1 As String, num2 As String)
Try
Dim result = SampleMethod(num1, num2)
WriteLine($"{num1} * {num2} = {result}")
Catch ex As Exception When DummyLog(ex)
' DummyLogメソッドは常にFalseを返すので、
' このcatchブロック内は実行されない
Catch ex As ArgumentException When ex.ParamName = "num1"
WriteLine($"■引数num1(値は{num1})によって例外が発生")
Catch ex As ArgumentException
WriteLine($"■num1以外の引数(値は{num2})によって例外が発生")
Catch ex As Exception
WriteLine($"■例外が発生:{ex.Message}")
End Try
End Sub
Sub Main()
CatchSample("2", "3")
' ⇒ 2 * 3 = 6
CatchSample("a", "3")
' ⇒ DummyLog: 整数に変換できない文字列
' パラメーター名: num1
' ■引数num1(値はa)によって例外が発生
CatchSample("2", "BB")
' ⇒ DummyLog: 整数に変換できない文字列
' パラメーター名: num2
' ■num1以外の引数(値はBB)によって例外が発生
CatchSample("123456789", "123")
' ⇒ DummyLog: 算術演算の結果オーバーフローが発生しました。
' ■例外が発生:算術演算の結果オーバーフローが発生しました。
#If DEBUG Then
ReadKey()
#End If
End Sub
End Module
絶対に実行されないcatch句というのはちょっとトリッキーではあるが、ご覧のように「全ての例外をロギングする」という目的を簡潔に記述できる。
なお、「SampleMethod」メソッドで使っているnameof演算子については「.NET TIPS:構文:文字列にクラス名などを間違えないようにコーディングするには?[C# 6.0]」をご覧いただきたい。
「DummyLog」メソッド/「CatchSample」メソッドに出てくるWriteLineメソッドの呼び出し方と、「Main」メソッド末尾のReadKeyメソッドの呼び出し方については、「.NET TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]」、または、「.NET TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?」をご覧いただきたい。
「Main」メソッド末尾にReadKeyメソッドを置く意味は、「.NET TIPS:Visual Studioでコンソール・アプリケーションのデバッグ実行時にコマンド・プロンプトを閉じないようにするには?」をご覧いただきたい。
WriteLineメソッドに引数として渡している「$」記号付きの文字列については、「.NET TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]」をご覧いただきたい。
まとめ
C# 6.0からは、VB同様に、catch句に条件を指定できるようになった。ちょっとトリッキーではあるが、それを応用して全ての例外をロギングする方法も紹介した。
利用可能バージョン:Visual Studio 2015(C# 6.0)以降
利用可能バージョン:Visual Studio .NET 2002(VB 7.0)以降
カテゴリ:C# 処理対象:言語構文
カテゴリ:Visual Basic .NET 処理対象:言語構文
関連TIPS:Visual Studioでコンソール・アプリケーションのデバッグ実行時にコマンド・プロンプトを閉じないようにするには?
関連TIPS:構文:文字列にクラス名などを間違えないようにコーディングするには?[C# 6.0]
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]
■この記事と関連性の高い別の.NET TIPS
Copyright© Digital Advantage Corp. All Rights Reserved.