一度に複数の例外をキャッチしますか?
-
02-07-2019 - |
質問
System.Exception
を単にキャッチすることは推奨されません。代わりに、「既知」のみが例外をキャッチする必要があります。
現在、これにより不必要な反復コードが発生する場合があります。例:
try
{
WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
WebId = Guid.Empty;
}
catch (OverflowException)
{
WebId = Guid.Empty;
}
不思議:両方の例外をキャッチし、 WebId = Guid.Empty
呼び出しを1回だけ呼び出す方法はありますか?
指定された例は、 GUID
。しかし、オブジェクトを複数回変更するコードを想像してください。操作の1つが予想どおりに失敗した場合、「リセット」したいでしょう。 オブジェクト
。ただし、予期しない例外が発生した場合は、それより高い値をスローする必要があります。
解決
System.Exception
をキャッチして、タイプをオンにします
catch (Exception ex)
{
if (ex is FormatException || ex is OverflowException)
{
WebId = Guid.Empty;
return;
}
throw;
}
他のヒント
編集: C#6.0の時点で、例外フィルターは完全に素晴らしい方法であると言っている他の人と同意します: catch(Exception ex)when(ex is ... || ex is ...)
1行のレイアウトがまだ嫌いで、次のようにコードを個人的にレイアウトすることを除いて。私は理解を改善すると信じているので、これは美的であると同時に機能的だと思います。反対する人もいます:
catch (Exception ex) when (
ex is ...
|| ex is ...
|| ex is ...
)
オリジナル:
ここでのパーティーに少し遅れていることは知っていますが、聖なる煙...
追跡をまっすぐにすると、この種の方法は以前の答えを複製しますが、複数の例外タイプに対して共通のアクションを実行し、1つのメソッドのスコープ内で全体を整然とした状態に保ちたい場合は、ラムダ/クロージャー/インライン関数を使用して、次のようなことをしますか?つまり、そのクロージャーを、あちこちで利用できる別の方法にしたいだけだということに気付く可能性はかなり高いです。しかし、その後、実際に残りのコードを構造的に変更することなく、それを行うのは非常に簡単です。そうですか
private void TestMethod ()
{
Action<Exception> errorHandler = ( ex ) => {
// write to a log, whatever...
};
try
{
// try some stuff
}
catch ( FormatException ex ) { errorHandler ( ex ); }
catch ( OverflowException ex ) { errorHandler ( ex ); }
catch ( ArgumentNullException ex ) { errorHandler ( ex ); }
}
私は仕方がありませんが(警告:少し皮肉/皮肉なことに)なぜこのような努力をして、基本的に次のものを置き換えるだけなのでしょうか>
try
{
// try some stuff
}
catch( FormatException ex ){}
catch( OverflowException ex ){}
catch( ArgumentNullException ex ){}
...次のコードの匂いのいくつかのクレイジーなバリエーションで、私はほんの一例であり、いくつかのキーストロークを保存しているふりをするだけです。
// sorta sucks, let's be honest...
try
{
// try some stuff
}
catch( Exception ex )
{
if (ex is FormatException ||
ex is OverflowException ||
ex is ArgumentNullException)
{
// write to a log, whatever...
return;
}
throw;
}
確かに自動的に読みやすくなるわけではないからです。
許可されて、 / *の3つの同一のインスタンスをログに書き込みましたが、何でも... * / return;
最初の例から>
しかし、それは私のポイントのようなものです。関数/メソッドのことを聞いたことがありますか?真剣に。一般的な ErrorHandler
関数を作成し、各catchブロックから呼び出すなど。
私に尋ねると、2番目の例( if
および is
キーワードを使用)は、可読性が大幅に低下すると同時に、メンテナンスフェーズでエラーが発生しやすくなります。プロジェクトの
プログラミングに比較的慣れていない人にとっては、メンテナンスフェーズはプロジェクトの全ライフタイムの98.7%以上を占めることになり、メンテナンスを行う貧弱なシュマックはほぼ間違いなくあなた以外の誰かになります。そして、彼らはあなたの名前を呪う仕事に彼らの時間の50%を費やす非常に良いチャンスがあります。
そしてもちろんFxCopはあなたに向かってksえるので、実行中のプログラムと正確にzipする属性をコードに 追加する必要があります。そこで、FxCopに99.9%のケースでフラグ付けが完全に正しいという問題を無視するように指示します。そして、申し訳ありませんが、私は間違っているかもしれませんが、それは「無視」しません;属性は実際にアプリにコンパイルされますか?
if
テスト全体を1行に入れると読みやすくなりますか?そうは思いません。つまり、1行にコードを追加すると「実行速度が速くなります」と強く主張する別のプログラマーがいました。しかし、もちろん彼は非常に熱狂的なナッツでした。インタプリタまたはコンパイラがその長い行を個別の1行ごとの命令文に分割する方法を、彼に説明しようとしています(まっすぐに-これは挑戦的でした)-彼が先に行った場合の結果と本質的に同じですコンパイラを上手にしようとする代わりに、コードを読みやすくしただけで、コンパイラには何の影響もありませんでした。しかし、私は脱線します。
これから1か月または2か月、さらに3種類の例外タイプを追加すると、これはどれだけ読みやすくなりますか? (回答: 多く 読みにくくなります。)
主要なポイントの1つは、実際に、私たち全員が毎日見ているテキストソースコードをフォーマットするポイントのほとんどが、実際に他の人間に明らかにすることであるということです。
他の人が指摘したように、catchブロック内で if
ステートメントを使用して、何が起こっているのかを判断できます。 C#6は例外フィルターをサポートしているため、次のように機能します。
try { … }
catch (Exception e) when (MyFilter(e))
{
…
}
MyFilter
メソッドは次のようになります。
private bool MyFilter(Exception e)
{
return e is ArgumentNullException || e is FormatException;
}
代わりに、これはすべてインラインで実行できます(whenステートメントの右側は、ブール式である必要があります)。
try { … }
catch (Exception e) when (e is ArgumentNullException || e is FormatException)
{
…
}
これは、 catch
ブロック内から if
ステートメントを使用する場合とは異なり、例外フィルターを使用するとスタックを巻き戻しません。
Visual Studio 2015 をダウンロードして、これを確認できますアウト。
Visual Studio 2013を引き続き使用する場合は、次のnugetパッケージをインストールできます。
インストールパッケージMicrosoft.Net.Compilers
このパッケージを参照すると、プロジェクトが 特定のバージョンのC#およびVisual Basicコンパイラに含まれる システムにインストールされたバージョンではなく、パッケージ。
残念ながら、C#にはありません。それを行うには例外フィルターが必要であり、C#はMSILの機能を公開しないためです。ただし、VB.NETにはこの機能があります。例:
Catch ex As Exception When TypeOf ex Is FormatException OrElse TypeOf ex Is OverflowException
できることは、匿名関数を使用してオンエラーコードをカプセル化し、特定のcatchブロックで呼び出すことです:
Action onError = () => WebId = Guid.Empty;
try
{
// something
}
catch (FormatException)
{
onError();
}
catch (OverflowException)
{
onError();
}
完全を期すため、 .NET 4.0 以降、コードは次のように書き換えることができます。
Guid.TryParse(queryString["web"], out WebId);
TryParse は例外をスローせず、falseを返します形式が間違っているため、WebIdを Guid.Empty
に設定しています。
C#7 以降、別の行に変数を導入することを回避できます。
Guid.TryParse(queryString["web"], out Guid webId);
返されるタプルを解析するためのメソッドを作成することもできます。これは、バージョン4.6の時点では.NET Frameworkではまだ使用できません。
(bool success, Guid result) TryParseGuid(string input) =>
(Guid.TryParse(input, out Guid result), result);
次のように使用します:
WebId = TryParseGuid(queryString["web"]).result;
// or
var tuple = TryParseGuid(queryString["web"]);
WebId = tuple.success ? tuple.result : DefaultWebId;
この役に立たない答えに対する次の役に立たない更新は、C#12でout-parametersの分解が実装されたときに行われます:)
アプリケーションをC#6にアップグレードできれば幸運です。新しいC#バージョンには、例外フィルターが実装されています。これを書くことができます:
catch (Exception ex) when (ex is FormatException || ex is OverflowException) {
WebId = Guid.Empty;
}
このコードは次と同じだと思う人もいます
catch (Exception ex) {
if (ex is FormatException || ex is OverflowException) {
WebId = Guid.Empty;
}
throw;
}
しかし、そうではありません。実際、これは以前のバージョンではエミュレートできないC#6の唯一の新機能です。まず、再スローはキャッチをスキップするよりもオーバーヘッドが大きいことを意味します。第二に、それは意味的に同等ではありません。新しい機能は、コードをデバッグするときにスタックをそのまま保持します。この機能がないと、クラッシュダンプはあまり役に立たないか、役に立たなくなります。
CodePlexに関するこれに関する議論を参照してください。そして、違いを示す例。
C#6+で例外フィルターが利用可能になりました。できます
try
{
WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when(ex is FormatException || ex is OverflowException)
{
WebId = Guid.Empty;
}
C#7.0+では、これをパターンマッチングと組み合わせることができます
try
{
await Task.WaitAll(tasks);
}
catch (Exception ex) when( ex is AggregateException ae )
{
//do something with members of ae, say ae.InnerExceptions
}
catch
スコープ内で if
ステートメントを使用したくない場合、 C#6.0
で Exception Filters
構文 を使用できます。これは、プレビューバージョンのCLRで既にサポートされていましたが、 VB.NET
/ MSIL
:
try
{
WebId = new Guid(queryString["web"]);
}
catch (Exception exception) when (exception is FormatException || ex is OverflowException)
{
WebId = Guid.Empty;
}
このコードは、 InvalidDataException
または ArgumentNullException
の場合にのみ Exception
をキャッチします。
実際、基本的には when
句の中に任意の条件を入れることができます:
static int a = 8;
...
catch (Exception exception) when (exception is InvalidDataException && a == 8)
{
Console.WriteLine("Catch");
}
catch
のスコープ内の if
ステートメントとは対照的に、 Exception Filters
は Exceptions
をスローできないことに注意してください。 >、およびそれらが実行される場合、または条件が true
でない場合、代わりに次の catch
条件が評価されます:
static int a = 7;
static int b = 0;
...
try
{
throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
Console.WriteLine("General catch");
}
出力:一般的なキャッチ。
複数の true
Exception Filter
がある場合-最初のものが受け入れられます:
static int a = 8;
static int b = 4;
...
try
{
throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
Console.WriteLine("General catch");
}
出力:キャッチ。
また、 MSIL
でわかるように、コードは if
ステートメントではなく、 Filters
および Exceptionsに変換されます
は、 Filter 1
および Filter 2
でマークされた領域内からスローできませんが、代わりに Exception
をスローするフィルターは失敗します。 endfilter
コマンドの前にスタックにプッシュされた最後の比較値は、フィルターの成功/失敗を決定します( Catch 1
XOR Catch 2
が適宜実行されます):
また、特に Guid
には Guid.TryParse
メソッド。
受け入れられた答えは受け入れられるように思われますが、CodeAnalysis / FxCop は、それが一般的な例外タイプをキャッチします。
また、「である」ようです;演算子はパフォーマンスをわずかに低下させる可能性があります。
CA1800:不必要にキャストしないでください は、「代わりに「as」演算子の結果をテストすることを検討する」と言いますが、そうすると、各例外を個別にキャッチする場合よりも多くのコードを書くことになります。
とにかく、私がすることは次のとおりです:
bool exThrown = false;
try
{
// Something
}
catch (FormatException) {
exThrown = true;
}
catch (OverflowException) {
exThrown = true;
}
if (exThrown)
{
// Something else
}
C#6で推奨されるアプローチは、例外フィルターを使用することです。例を次に示します。
try
{
throw new OverflowException();
}
catch(Exception e ) when ((e is DivideByZeroException) || (e is OverflowException))
{
// this will execute iff e is DividedByZeroEx or OverflowEx
Console.WriteLine("E");
}
C#7 Michael Stumからの回答は、switchステートメントの読みやすさを維持しながら改善できます。
catch (Exception ex)
{
switch (ex)
{
case FormatException _:
case OverflowException _:
WebId = Guid.Empty;
break;
default:
throw;
}
}
これは、Mattの回答の変形です(これは少しすっきりしているように感じます)...メソッドを使用します:
public void TryCatch(...)
{
try
{
// something
return;
}
catch (FormatException) {}
catch (OverflowException) {}
WebId = Guid.Empty;
}
その他の例外はスローされ、コード WebId = Guid.Empty;
はヒットしません。他の例外がプログラムをクラッシュさせたくない場合は、他の2つのキャッチの後にこれを追加してください:
...
catch (Exception)
{
// something, if anything
return; // only need this if you follow the example I gave and put it all in a method
}
Joseph Daigleの回答は良い解決策ですが、私は見つけました次の構造は少し整理され、エラーが発生しにくくなります。
catch(Exception ex)
{
if (!(ex is SomeException || ex is OtherException)) throw;
// Handle exception
}
式を逆にすることにはいくつかの利点があります:
- returnステートメントは不要です
- コードはネストされていません
- ジョセフの解法では式から分離されている「スロー」または「リターン」ステートメントを忘れる恐れはありません。
1行に圧縮することもできます(非常にきれいではありませんが)
catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw;
// Handle exception
}
編集: C#6.0の例外フィルタリング構文が少し簡潔になり、現在のソリューションよりも他の多くの利点が付属します。 (最も顕著なのは、スタックを無傷のままにする)
C#6.0構文を使用して同じ問題がどのように見えるかを以下に示します。
catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
// Handle exception
}
@Micheal
コードのわずかに改訂されたバージョン:
catch (Exception ex)
{
Type exType = ex.GetType();
if (exType == typeof(System.FormatException) ||
exType == typeof(System.OverflowException)
{
WebId = Guid.Empty;
} else {
throw;
}
}
文字列の比較はugくて遅いです。
方法について
try
{
WebId = Guid.Empty;
WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
注意および警告:さらに別の種類、機能的なスタイル。
リンクにあるものはあなたの質問に直接答えませんが、次のように拡張するのは簡単です:
static void Main()
{
Action body = () => { ...your code... };
body.Catch<InvalidOperationException>()
.Catch<BadCodeException>()
.Catch<AnotherException>(ex => { ...handler... })();
}
(基本的に、それ自体を返す別の空の Catch
オーバーロードを提供します)
これに対するより大きな質問は、なぜです。ここでのコストが利益を上回るとは思わない:)
catch (Exception ex)
{
if (!(
ex is FormatException ||
ex is OverflowException))
{
throw;
}
Console.WriteLine("Hello");
}
2015-12-15の更新:Cについては https://stackoverflow.com/a/22864936/1718702 を参照#6。よりクリーンで、言語の標準になりました。
一度キャッチして例外をフィルタリングするためのよりエレガントなソリューションが必要な人向けに、実証された拡張メソッドを使用します以下。
この拡張機能はライブラリに既にあり、元々は他の目的のために書かれていましたが、例外の type
のチェックには完全に機能しました。さらに、私見では、 ||
文の束よりもきれいに見えます。また、受け入れられた答えとは異なり、明示的な例外処理を好むので、 ex is ...
は、派生クラスが親タイプに割り当て可能であるため、望ましくない動作をしました。
使用法
if (ex.GetType().IsAnyOf(
typeof(FormatException),
typeof(ArgumentException)))
{
// Handle
}
else
throw;
IsAnyOf.cs Extension(依存関係の完全なエラー処理の例を参照)
namespace Common.FluentValidation
{
public static partial class Validate
{
/// <summary>
/// Validates the passed in parameter matches at least one of the passed in comparisons.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="p_parameter">Parameter to validate.</param>
/// <param name="p_comparisons">Values to compare against.</param>
/// <returns>True if a match is found.</returns>
/// <exception cref="ArgumentNullException"></exception>
public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
{
// Validate
p_parameter
.CannotBeNull("p_parameter");
p_comparisons
.CannotBeNullOrEmpty("p_comparisons");
// Test for any match
foreach (var item in p_comparisons)
if (p_parameter.Equals(item))
return true;
// Return no matches found
return false;
}
}
}
完全なエラー処理の例(新しいコンソールアプリへのコピーと貼り付け)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.FluentValidation;
namespace IsAnyOfExceptionHandlerSample
{
class Program
{
static void Main(string[] args)
{
// High Level Error Handler (Log and Crash App)
try
{
Foo();
}
catch (OutOfMemoryException ex)
{
Console.WriteLine("FATAL ERROR! System Crashing. " + ex.Message);
Console.ReadKey();
}
}
static void Foo()
{
// Init
List<Action<string>> TestActions = new List<Action<string>>()
{
(key) => { throw new FormatException(); },
(key) => { throw new ArgumentException(); },
(key) => { throw new KeyNotFoundException();},
(key) => { throw new OutOfMemoryException(); },
};
// Run
foreach (var FooAction in TestActions)
{
// Mid-Level Error Handler (Appends Data for Log)
try
{
// Init
var SomeKeyPassedToFoo = "FooParam";
// Low-Level Handler (Handle/Log and Keep going)
try
{
FooAction(SomeKeyPassedToFoo);
}
catch (Exception ex)
{
if (ex.GetType().IsAnyOf(
typeof(FormatException),
typeof(ArgumentException)))
{
// Handle
Console.WriteLine("ex was {0}", ex.GetType().Name);
Console.ReadKey();
}
else
{
// Add some Debug info
ex.Data.Add("SomeKeyPassedToFoo", SomeKeyPassedToFoo.ToString());
throw;
}
}
}
catch (KeyNotFoundException ex)
{
// Handle differently
Console.WriteLine(ex.Message);
int Count = 0;
if (!Validate.IsAnyNull(ex, ex.Data, ex.Data.Keys))
foreach (var Key in ex.Data.Keys)
Console.WriteLine(
"[{0}][\"{1}\" = {2}]",
Count, Key, ex.Data[Key]);
Console.ReadKey();
}
}
}
}
}
namespace Common.FluentValidation
{
public static partial class Validate
{
/// <summary>
/// Validates the passed in parameter matches at least one of the passed in comparisons.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="p_parameter">Parameter to validate.</param>
/// <param name="p_comparisons">Values to compare against.</param>
/// <returns>True if a match is found.</returns>
/// <exception cref="ArgumentNullException"></exception>
public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
{
// Validate
p_parameter
.CannotBeNull("p_parameter");
p_comparisons
.CannotBeNullOrEmpty("p_comparisons");
// Test for any match
foreach (var item in p_comparisons)
if (p_parameter.Equals(item))
return true;
// Return no matches found
return false;
}
/// <summary>
/// Validates if any passed in parameter is equal to null.
/// </summary>
/// <param name="p_parameters">Parameters to test for Null.</param>
/// <returns>True if one or more parameters are null.</returns>
public static bool IsAnyNull(params object[] p_parameters)
{
p_parameters
.CannotBeNullOrEmpty("p_parameters");
foreach (var item in p_parameters)
if (item == null)
return true;
return false;
}
}
}
namespace Common.FluentValidation
{
public static partial class Validate
{
/// <summary>
/// Validates the passed in parameter is not null, throwing a detailed exception message if the test fails.
/// </summary>
/// <param name="p_parameter">Parameter to validate.</param>
/// <param name="p_name">Name of tested parameter to assist with debugging.</param>
/// <exception cref="ArgumentNullException"></exception>
public static void CannotBeNull(this object p_parameter, string p_name)
{
if (p_parameter == null)
throw
new
ArgumentNullException(
string.Format("Parameter \"{0}\" cannot be null.",
p_name), default(Exception));
}
}
}
namespace Common.FluentValidation
{
public static partial class Validate
{
/// <summary>
/// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="p_parameter">Parameter to validate.</param>
/// <param name="p_name">Name of tested parameter to assist with debugging.</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static void CannotBeNullOrEmpty<T>(this ICollection<T> p_parameter, string p_name)
{
if (p_parameter == null)
throw new ArgumentNullException("Collection cannot be null.\r\nParameter_Name: " + p_name, default(Exception));
if (p_parameter.Count <= 0)
throw new ArgumentOutOfRangeException("Collection cannot be empty.\r\nParameter_Name: " + p_name, default(Exception));
}
/// <summary>
/// Validates the passed in parameter is not null or empty, throwing a detailed exception message if the test fails.
/// </summary>
/// <param name="p_parameter">Parameter to validate.</param>
/// <param name="p_name">Name of tested parameter to assist with debugging.</param>
/// <exception cref="ArgumentException"></exception>
public static void CannotBeNullOrEmpty(this string p_parameter, string p_name)
{
if (string.IsNullOrEmpty(p_parameter))
throw new ArgumentException("String cannot be null or empty.\r\nParameter_Name: " + p_name, default(Exception));
}
}
}
2つのNUnitユニットテストのサンプル
Exception
タイプのマッチング動作は正確です(つまり、子は親タイプのいずれとも一致しません)。
using System;
using System.Collections.Generic;
using Common.FluentValidation;
using NUnit.Framework;
namespace UnitTests.Common.Fluent_Validations
{
[TestFixture]
public class IsAnyOf_Tests
{
[Test, ExpectedException(typeof(ArgumentNullException))]
public void IsAnyOf_ArgumentNullException_ShouldNotMatch_ArgumentException_Test()
{
Action TestMethod = () => { throw new ArgumentNullException(); };
try
{
TestMethod();
}
catch (Exception ex)
{
if (ex.GetType().IsAnyOf(
typeof(ArgumentException), /*Note: ArgumentNullException derrived from ArgumentException*/
typeof(FormatException),
typeof(KeyNotFoundException)))
{
// Handle expected Exceptions
return;
}
//else throw original
throw;
}
}
[Test, ExpectedException(typeof(OutOfMemoryException))]
public void IsAnyOf_OutOfMemoryException_ShouldMatch_OutOfMemoryException_Test()
{
Action TestMethod = () => { throw new OutOfMemoryException(); };
try
{
TestMethod();
}
catch (Exception ex)
{
if (ex.GetType().IsAnyOf(
typeof(OutOfMemoryException),
typeof(StackOverflowException)))
throw;
/*else... Handle other exception types, typically by logging to file*/
}
}
}
}
これらの答えが表面に触れただけだと感じたので、もう少し深く掘り下げようとしました。
だから、私たちが本当にやりたいことは、コンパイルしないものです、と言います:
// Won't compile... damn
public static void Main()
{
try
{
throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException)
catch (IndexOutOfRangeException)
{
// ... handle
}
これが必要な理由は、プロセスの後半で必要なものを例外ハンドラでキャッチしたくないためです。確かに、例外をキャッチし、「if」で何をすべきかを確認することはできますが、正直なところ、私たちは本当にそれを望んでいません。 (FxCop、デバッガーの問題、ugさ)
では、なぜこのコードがコンパイルされないのでしょうか?そして、どうやってそれをハックして、どうなるのでしょうか?
コードを見ると、私たちが本当にやりたいことは呼び出しを転送することです。ただし、MSパーティションIIによれば、IL例外ハンドラーブロックはこのようには機能しません。この場合、「例外」オブジェクトが異なるタイプを持つことができることを意味するため、これは理にかなっています。
またはコードで記述するために、コンパイラーに次のようなことをするように依頼します(それは完全に正しいわけではありませんが、私が推測する最も近いものです):
// Won't compile... damn
try
{
throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) {
goto theOtherHandler;
}
catch (IndexOutOfRangeException e) {
theOtherHandler:
Console.WriteLine("Handle!");
}
これがコンパイルされない理由は非常に明白です。「$ exception」オブジェクトにはどのタイプと値がありますか(変数「e」に格納されています)?コンパイラがこれを処理する方法は、両方の例外の共通の基本型が「例外」であることに注意し、変数に両方の例外を含めるために使用し、キャッチされた2つの例外のみを処理することです。これをILで実装する方法は、VB.Netで利用可能な「フィルター」としてです。
C#で動作させるには、適切な 'Exception'ベースタイプを持つ一時変数が必要です。コードのフローを制御するために、いくつかのブランチを追加できます。ここに行きます:
Exception ex;
try
{
throw new ArgumentException(); // for demo purposes; won't be caught.
goto noCatch;
}
catch (ArgumentOutOfRangeException e) {
ex = e;
}
catch (IndexOutOfRangeException e) {
ex = e;
}
Console.WriteLine("Handle the exception 'ex' here :-)");
// throw ex ?
noCatch:
Console.WriteLine("We're done with the exception handling.");
これの明らかな欠点は、適切に再スローできないことです。正直に言って、これは非常にugい解決策です。ブランチの削除を実行することでい部分を少し修正できます。これにより、ソリューションが少し改善されます。
Exception ex = null;
try
{
throw new ArgumentException();
}
catch (ArgumentOutOfRangeException e)
{
ex = e;
}
catch (IndexOutOfRangeException e)
{
ex = e;
}
if (ex != null)
{
Console.WriteLine("Handle the exception here :-)");
}
それは「再投げ」だけを残します。これが機能するためには、「catch」ブロック内で処理を実行できる必要があります。この機能を実現する唯一の方法は、「Exception」オブジェクトをキャッチすることです。
この時点で、オーバーロード解決を使用して、または例外を処理するために、さまざまなタイプの例外を処理する別個の関数を追加できます。どちらにも欠点があります。まず、ヘルパー関数を使用してこれを行う方法を次に示します。
private static bool Handle(Exception e)
{
Console.WriteLine("Handle the exception here :-)");
return true; // false will re-throw;
}
public static void Main()
{
try
{
throw new OutOfMemoryException();
}
catch (ArgumentException e)
{
if (!Handle(e)) { throw; }
}
catch (IndexOutOfRangeException e)
{
if (!Handle(e)) { throw; }
}
Console.WriteLine("We're done with the exception handling.");
他の解決策は、Exceptionオブジェクトをキャッチし、それに応じて処理することです。上記のコンテキストに基づいた、これに関する最もリテラルな翻訳は次のとおりです。
try
{
throw new ArgumentException();
}
catch (Exception e)
{
Exception ex = (Exception)(e as ArgumentException) ?? (e as IndexOutOfRangeException);
if (ex != null)
{
Console.WriteLine("Handle the exception here :-)");
// throw ?
}
else
{
throw;
}
}
結論:
- 再スローしたくない場合は、適切な例外をキャッチして、一時的に保存することを検討できます。
- ハンドラーが単純で、コードを再利用したい場合、おそらく最良の解決策はヘルパー関数を導入することです。
- 再スローする場合、コードを「Exception」キャッチハンドラーに入れる以外に選択肢はありません。これにより、FxCopとデバッガーのキャッチされない例外が壊れます。
これは、すべてのC#開発者が最終的に直面する古典的な問題です。
質問を2つの質問に分けてみましょう。最初の、
一度に複数の例外をキャッチできますか
要するに、いいえ。
次の質問につながる
同じcatch()ブロックで複数の例外タイプをキャッチできない場合、重複コードを記述しないようにするにはどうすればよいですか?
フォールバック値を作成するのが安価な特定のサンプルを考えると、次の手順に従うのが好きです:
- WebIdをフォールバック値に初期化します。
- 一時変数に新しいGUIDを構築します。
- WebIdを完全に構築された一時変数に設定します。これをtry {}ブロックの最後のステートメントにします。
コードは次のようになります:
try
{
WebId = Guid.Empty;
Guid newGuid = new Guid(queryString["web"]);
// More initialization code goes here like
// newGuid.x = y;
WebId = newGuid;
}
catch (FormatException) {}
catch (OverflowException) {}
例外がスローされた場合、WebIdは半分構築された値に設定されることはなく、Guid.Emptyのままです。
フォールバック値の構築に費用がかかり、値のリセットがはるかに安価な場合、リセットコードを独自の関数に移動します。
try
{
WebId = new Guid(queryString["web"]);
// More initialization code goes here.
}
catch (FormatException) {
Reset(WebId);
}
catch (OverflowException) {
Reset(WebId);
}
では、すべての例外スイッチ内で大量のコードを繰り返し使用していますか?メソッドを抽出することは神のアイデアのように聞こえますが、そうではありませんか?
コードは次のようになります:
MyClass instance;
try { instance = ... }
catch(Exception1 e) { Reset(instance); }
catch(Exception2 e) { Reset(instance); }
catch(Exception) { throw; }
void Reset(MyClass instance) { /* reset the state of the instance */ }
そのコードの重複に誰も気付いていないのはなぜだろうか。
C#6からは、さらに他の人が既に述べたように exception-filters があります。したがって、上記のコードを次のように変更できます。
try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{
Reset(instance);
}
このすでに長いスレッドに短い回答を追加したいと思います。言及されていないことは、catchステートメントの優先順位です。より具体的には、キャッチしようとしている各タイプの例外のスコープに注意する必要があります。
たとえば、&quot; catch-all&quot;を使用する場合、 Exception としての例外は、他のすべてのcatchステートメントに先行し、明らかにコンパイラエラーが発生しますが、順序を逆にすると、catchステートメント(アンチパターンのビット)を連鎖させることができますcatch-all Exception を下部に入力すると、try..catchブロックの上位に対応しなかった例外がキャプチャされます。
try
{
// do some work here
}
catch (WebException ex)
{
// catch a web excpetion
}
catch (ArgumentException ex)
{
// do some stuff
}
catch (Exception ex)
{
// you should really surface your errors but this is for example only
throw new Exception("An error occurred: " + ex.Message);
}
このMSDNドキュメントを確認することを強くお勧めします:
catch句内にないコードの他の部分で行うように、メソッドに共通のコードを配置するなど、コードをシンプルに保つことを試みますか?
例:
try
{
// ...
}
catch (FormatException)
{
DoSomething();
}
catch (OverflowException)
{
DoSomething();
}
// ...
private void DoSomething()
{
// ...
}
どのように行うか、シンプルで美しいパターンを見つけようとする
1つの方法を見つけましたが、これは The Daily WTFの資料のように見えます。 :
catch (Exception ex)
{
switch (ex.GetType().Name)
{
case "System.FormatException":
case "System.OverflowException":
WebId = Guid.Empty;
break;
default:
throw;
}
}
ここで言及する価値があります。複数の組み合わせ(Exception errorとexception.message)に応答できます。
TextGrid、TextBlock、またはCheckBoxのいずれかのコンテンツを使用して、データグリッドでコントロールオブジェクトをキャストしようとしたときに、ユースケースシナリオに遭遇しました。この場合、返された例外は同じでしたが、メッセージは異なりました。
try
{
//do something
}
catch (Exception ex) when (ex.Message.Equals("the_error_message1_here"))
{
//do whatever you like
}
catch (Exception ex) when (ex.Message.Equals("the_error_message2_here"))
{
//do whatever you like
}
最短の回答を提案したい(もう1つの機能スタイル):
Catch<FormatException, OverflowException>(() =>
{
WebId = new Guid(queryString["web"]);
},
exception =>
{
WebId = Guid.Empty;
});
このためには、いくつかの&quot; Catch&quot;を作成する必要があります。 System.Actionに似たメソッドのオーバーロード:
[DebuggerNonUserCode]
public static void Catch<TException1, TException2>(Action tryBlock,
Action<Exception> catchBlock)
{
CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2));
}
[DebuggerNonUserCode]
public static void Catch<TException1, TException2, TException3>(Action tryBlock,
Action<Exception> catchBlock)
{
CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2), typeof(TException3));
}
など、必要な数だけ。ただし、一度行う必要があり、すべてのプロジェクトで使用できます(または、nugetパッケージを作成した場合は、それも使用できます)。
およびCatchManyの実装:
[DebuggerNonUserCode]
public static void CatchMany(Action tryBlock, Action<Exception> catchBlock,
params Type[] exceptionTypes)
{
try
{
tryBlock();
}
catch (Exception exception)
{
if (exceptionTypes.Contains(exception.GetType())) catchBlock(exception);
else throw;
}
}
p.s。コードを簡単にするためにnullチェックを入れていません。パラメーター検証を追加することを検討してください。
p.s.2 catchから値を返したい場合、同じCatchメソッドを実行する必要がありますが、パラメーターのActionの代わりにreturnとFuncを使用します。
tryとcatchを2回呼び出すだけです。
try
{
WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
WebId = Guid.Empty;
}
try
{
WebId = new Guid(queryString["web"]);
}
catch (OverflowException)
{
WebId = Guid.Empty;
}
それは単純です!!
C#6.0では、例外フィルターは例外処理の改善です
try
{
DoSomeHttpRequest();
}
catch (System.Web.HttpException e)
{
switch (e.GetHttpCode())
{
case 400:
WriteLine("Bad Request");
case 500:
WriteLine("Internal Server Error");
default:
WriteLine("Generic Error");
}
}