サーチ…
基本的な例外処理
try
{
/* code that could throw an exception */
}
catch (Exception ex)
{
/* handle the exception */
}
同じコードですべての例外を処理することは、しばしば最善の方法ではないことに注意してください。
これは、最後の手段として、内部例外処理ルーチンが失敗した場合によく使用されます。
特定の例外タイプの処理
try
{
/* code to open a file */
}
catch (System.IO.FileNotFoundException)
{
/* code to handle the file being not found */
}
catch (System.IO.UnauthorizedAccessException)
{
/* code to handle not being allowed access to the file */
}
catch (System.IO.IOException)
{
/* code to handle IOException or it's descendant other than the previous two */
}
catch (System.Exception)
{
/* code to handle other errors */
}
例外は順番に評価され、継承が適用されるように注意してください。だから、あなたは最も具体的なものから始め、彼らの祖先で終わる必要があります。任意の時点で、1つのキャッチブロックのみが実行されます。
例外オブジェクトの使用
独自のコードで例外を作成してスローすることは許可されています。例外のインスタンス化は、他のC#オブジェクトと同じ方法で行われます。
Exception ex = new Exception();
// constructor with an overload that takes a message string
Exception ex = new Exception("Error message");
throw
キーワードを使用して例外を発生させることができます。
try
{
throw new Exception("Error");
}
catch (Exception ex)
{
Console.Write(ex.Message); // Logs 'Error' to the output window
}
注意:キャッチブロック内に新しい例外を投げる場合は、元の例外が "内部例外"として渡されるようにしてください。
void DoSomething()
{
int b=1; int c=5;
try
{
var a = 1;
b = a - 1;
c = a / b;
a = a / c;
}
catch (DivideByZeroException dEx) when (b==0)
{
// we're throwing the same kind of exception
throw new DivideByZeroException("Cannot divide by b because it is zero", dEx);
}
catch (DivideByZeroException dEx) when (c==0)
{
// we're throwing the same kind of exception
throw new DivideByZeroException("Cannot divide by c because it is zero", dEx);
}
}
void Main()
{
try
{
DoSomething();
}
catch (Exception ex)
{
// Logs full error information (incl. inner exception)
Console.Write(ex.ToString());
}
}
この場合、例外を処理することはできませんが、いくつかの有用な情報がメッセージに追加されます(元の例外には外部例外ブロックによってex.InnerException
を介してアクセスできます)。
それは次のように表示されます:
System.DivideByZeroException:ゼロであるためbで割り切れません---> System.DivideByZeroException:0で除算しようとしました。
C:[...]のUserQuery.g__DoSomething0_0()で\ LINQPadQuery.cs:行36
---内部例外スタックトレースの終了---
C:[...]のUserQuery.g__DoSomething0_0()で\ LINQPadQuery.cs:行42
UserQuery.Main()でC:[...] \ LINQPadQuery.cs:行55
この例をLinqPadで試している場合、行番号はそれほど意味がありません(いつもあなたを助けるとは限りません)。しかし、上に示唆したように参考になるエラーテキストを渡すと、エラーの場所を追跡する時間が大幅に短縮されます。この例では、
c = a / b;
関数DoSomething()
します。
最後にブロック
try
{
/* code that could throw an exception */
}
catch (Exception)
{
/* handle the exception */
}
finally
{
/* Code that will be executed, regardless if an exception was thrown / caught or not */
}
try / catch / finally
ブロックは、ファイルから読み込むときに非常に便利です。
例えば:
FileStream f = null;
try
{
f = File.OpenRead("file.txt");
/* process the file here */
}
finally
{
f?.Close(); // f may be null, so use the null conditional operator.
}
tryブロックの後には、 catch
ブロックまたはfinally
ブロックのいずれかが続かなければなりません。ただし、キャッチブロックが存在しないため、実行によって終了されます。終了する前に、finallyブロック内のステートメントが実行されます。
ファイル読み込みでは、 FileStream
( OpenRead
が返すもの)としてusing
ブロックを使用していた可能性があります。これはIDisposable
実装しています。
try
ブロックにreturn
文があっても、 finally
ブロックは通常実行されます。それができない場合がいくつかあります:
- StackOverflowが発生したとき。
-
Environment.FailFast
- アプリケーションプロセスは、通常は外部ソースによって強制終了されます。
WCFサービス用のIErrorHandlerの実装
WCFサービス用にIErrorHandlerを実装することは、エラー処理とロギングを集中管理するための優れた方法です。ここに示す実装は、WCFサービスの1つを呼び出した結果としてスローされる未処理の例外をすべて捕捉する必要があります。この例では、カスタムオブジェクトを返す方法と、デフォルトのXMLではなくJSONを返す方法も示しています。
IErrorHandlerを実装する:
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Runtime.Serialization.Json;
using System.ServiceModel;
using System.ServiceModel.Web;
namespace BehaviorsAndInspectors
{
public class ErrorHandler : IErrorHandler
{
public bool HandleError(Exception ex)
{
// Log exceptions here
return true;
} // end
public void ProvideFault(Exception ex, MessageVersion version, ref Message fault)
{
// Get the outgoing response portion of the current context
var response = WebOperationContext.Current.OutgoingResponse;
// Set the default http status code
response.StatusCode = HttpStatusCode.InternalServerError;
// Add ContentType header that specifies we are using JSON
response.ContentType = new MediaTypeHeaderValue("application/json").ToString();
// Create the fault message that is returned (note the ref parameter) with BaseDataResponseContract
fault = Message.CreateMessage(
version,
string.Empty,
new CustomReturnType { ErrorMessage = "An unhandled exception occurred!" },
new DataContractJsonSerializer(typeof(BaseDataResponseContract), new List<Type> { typeof(BaseDataResponseContract) }));
if (ex.GetType() == typeof(VariousExceptionTypes))
{
// You might want to catch different types of exceptions here and process them differently
}
// Tell WCF to use JSON encoding rather than default XML
var webBodyFormatMessageProperty = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, webBodyFormatMessageProperty);
} // end
} // end class
} // end namespace
この例では、サービスの振る舞いにハンドラをアタッチします。同様にIEndpointBehavior、IContractBehavior、またはIOperationBehaviorにこれを添付することもできます。
サービスビヘイビアへのアタッチ:
using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace BehaviorsAndInspectors
{
public class ErrorHandlerExtension : BehaviorExtensionElement, IServiceBehavior
{
public override Type BehaviorType
{
get { return GetType(); }
}
protected override object CreateBehavior()
{
return this;
}
private IErrorHandler GetInstance()
{
return new ErrorHandler();
}
void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } // end
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
var errorHandlerInstance = GetInstance();
foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
dispatcher.ErrorHandlers.Add(errorHandlerInstance);
}
}
void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } // end
} // end class
} // end namespace
Web.configの構成:
...
<system.serviceModel>
<services>
<service name="WebServices.MyService">
<endpoint binding="webHttpBinding" contract="WebServices.IMyService" />
</service>
</services>
<extensions>
<behaviorExtensions>
<!-- This extension if for the WCF Error Handling-->
<add name="ErrorHandlerBehavior" type="WebServices.BehaviorsAndInspectors.ErrorHandlerExtensionBehavior, WebServices, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<ErrorHandlerBehavior />
</behavior>
</serviceBehaviors>
</behaviors>
....
</system.serviceModel>
...
このトピックで役立つリンクがいくつかあります:
https://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler(v=vs.100).aspx
その他の例:
HTTPステータスコードが401である場合、不正なメッセージ本文を返すIErrorHandler
IErrorHandlerはWCFのエラーを処理していないようです。
どのようにカスタムWCFエラーハンドラを非OKのhttpコードでJSON応答を返すようにするには?
HttpClientリクエストのContent-Typeヘッダーはどのように設定しますか?
カスタム例外の作成
他の例外と同様にスローされるカスタム例外を実装することができます。これは、実行時に例外を他のエラーと区別できるようにしたい場合に適しています。
この例では、複雑な入力を解析する際にアプリケーションで発生する可能性がある問題を明確に処理するためのカスタム例外を作成します。
カスタム例外クラスの作成
カスタム例外を作成するには、例外のサブクラスを作成しException
。
public class ParserException : Exception
{
public ParserException() :
base("The parsing went wrong and we have no additional information.") { }
}
カスタム例外は、キャッチャーに追加情報を提供する場合に非常に便利になります。
public class ParserException : Exception
{
public ParserException(string fileName, int lineNumber) :
base($"Parser error in {fileName}:{lineNumber}")
{
FileName = fileName;
LineNumber = lineNumber;
}
public string FileName {get; private set;}
public int LineNumber {get; private set;}
}
さて、 catch(ParserException x)
をcatch(ParserException x)
すると、例外処理を微調整するための追加のセマンティクスが得られます。
カスタムクラスは、追加のシナリオをサポートするために次の機能を実装できます。
再投げ
解析プロセス中、元の例外は依然として重要です。この例では、コードは数値であると予想される文字列を解析しようとしているため、 FormatException
です。この場合、カスタム例外は ' InnerException 'のインクルードをサポートする必要があります。
//new constructor:
ParserException(string msg, Exception inner) : base(msg, inner) {
}
直列化
場合によっては、例外がAppDomainの境界を越えなければならないことがあります。これは、新しいパーサー構成のホットリロードをサポートするために、パーサーが独自のAppDomainで実行されている場合です。 Visual Studioでは、 Exception
テンプレートを使用してこのようなコードを生成できます。
[Serializable]
public class ParserException : Exception
{
// Constructor without arguments allows throwing your exception without
// providing any information, including error message. Should be included
// if your exception is meaningful without any additional details. Should
// set message by calling base constructor (default message is not helpful).
public ParserException()
: base("Parser failure.")
{}
// Constructor with message argument allows overriding default error message.
// Should be included if users can provide more helpful messages than
// generic automatically generated messages.
public ParserException(string message)
: base(message)
{}
// Constructor for serialization support. If your exception contains custom
// properties, read their values here.
protected ParserException(SerializationInfo info, StreamingContext context)
: base(info, context)
{}
}
ParserExceptionの使用
try
{
Process.StartRun(fileName)
}
catch (ParserException ex)
{
Console.WriteLine($"{ex.Message} in ${ex.FileName}:${ex.LineNumber}");
}
catch (PostProcessException x)
{
...
}
例外のキャッチとラップにカスタム例外を使用することもできます。このようにして、多くの異なるエラーをアプリケーションにとってより有用な単一のエラータイプに変換することができます。
try
{
int foo = int.Parse(token);
}
catch (FormatException ex)
{
//Assuming you added this constructor
throw new ParserException(
$"Failed to read {token} as number.",
FileName,
LineNumber,
ex);
}
独自のカスタム例外を発生させて例外を処理する場合は、一般に、上記のようにInnerException
プロパティに元の例外を参照する必要があります。
セキュリティ上の懸念
例外の理由を公開すると、ユーザーがアプリケーションの内部動作を見ることを許可することによってセキュリティが損なわれる可能性がある場合は、内部例外をラップするのは悪い考えです。これは、他人が使用するクラスライブラリを作成する場合に適用されます。
内部例外をラップせずにカスタム例外を発生させる方法は次のとおりです。
try
{
// ...
}
catch (SomeStandardException ex)
{
// ...
throw new MyCustomException(someMessage);
}
結論
ラップするかアンラップされた新しい例外を使用してカスタム例外を呼び出す場合は、呼び出し元にとって意味のある例外を発生させる必要があります。例えば、クラスライブラリのユーザは、そのライブラリがその内部作業をどのようにしているかについて多くを知らないかもしれない。クラスライブラリの依存関係によってスローされる例外は意味がありません。むしろ、ユーザは、クラスライブラリがこれらの依存関係を誤った方法でどのように使用しているかに関係する例外を望む。
try
{
// ...
}
catch (IOException ex)
{
// ...
throw new StorageServiceException(@"The Storage Service encountered a problem saving
your data. Please consult the inner exception for technical details.
If you are not able to resolve the problem, please call 555-555-1234 for technical
assistance.", ex);
}
例外アンチパターン
例外を飲み込む
次のように例外を常に再スローする必要があります。
try
{
...
}
catch (Exception ex)
{
...
throw;
}
以下のような例外を再スローすると元の例外がわかりにくくなり、元のスタックトレースが失われます。誰もこれをしてはいけません!キャッチと再スロー前のスタックトレースは失われます。
try
{
...
}
catch (Exception ex)
{
...
throw ex;
}
野球の例外処理
if-then文やwhileループのような通常のフロー制御構造の代わりに例外を使うべきではありません。このアンチパターンは、 野球の例外処理と呼ばれることがあります 。
アンチパターンの例を次に示します。
try
{
while (AccountManager.HasMoreAccounts())
{
account = AccountManager.GetNextAccount();
if (account.Name == userName)
{
//We found it
throw new AccountFoundException(account);
}
}
}
catch (AccountFoundException found)
{
Console.Write("Here are your account details: " + found.Account.Details.ToString());
}
ここでそれを行うより良い方法です:
Account found = null;
while (AccountManager.HasMoreAccounts() && (found==null))
{
account = AccountManager.GetNextAccount();
if (account.Name == userName)
{
//We found it
found = account;
}
}
Console.Write("Here are your account details: " + found.Details.ToString());
キャッチ(例外)
あなたのコードで一般的な例外の型をキャッチする理由はほとんどありません(何もない!)理由があります。それ以外の場合は、コードのバグを隠すため、発生すると予想される例外の型だけをキャッチする必要があります。
try
{
var f = File.Open(myfile);
// do something
}
catch (Exception x)
{
// Assume file not found
Console.Write("Could not open file");
// but maybe the error was a NullReferenceException because of a bug in the file handling code?
}
より良い:
try
{
var f = File.Open(myfile);
// do something which should normally not throw exceptions
}
catch (IOException)
{
Console.Write("File not found");
}
// Unfortunatelly, this one does not derive from the above, so declare separatelly
catch (UnauthorizedAccessException)
{
Console.Write("Insufficient rights");
}
それ以外の例外が発生した場合は、意図的にアプリケーションがクラッシュするため、デバッガで直接処理され、問題を修正できます。とにかくこれら以外の例外が発生した場合は、プログラムを出荷しないでください。クラッシュすることは問題ではありません。
以下は悪い例でもあります。例外を使用してプログラミングエラーを回避するためです。それは彼らのために設計されたものではありません。
public void DoSomething(String s)
{
if (s == null)
throw new ArgumentNullException(nameof(s));
// Implementation goes here
}
try
{
DoSomething(myString);
}
catch(ArgumentNullException x)
{
// if this happens, we have a programming error and we should check
// why myString was null in the first place.
}
1つのメソッドから複数の例外を集約する
1つの方法で複数の例外をスローすることはできません。 AggregateExceptionsを使いこなすことに慣れていない場合は、間違ったことを多く表すために独自のデータ構造を作成したくなるかもしれません。もちろん、例外ではない別のデータ構造が検証の結果などより理想的です。 AggregateExceptionsを使って遊んでいても、受信側にいるかもしれませんし、常にそれらを処理していることに気付いていないと、あなたに役立つかもしれません。
メソッドを実行することは非常に妥当であり、たとえそれが全体として失敗に終わっても、スローされた例外に間違っていた複数の事柄を強調したいと思うでしょう。一例として、この動作は、並列メソッドがどのように複数のスレッドに分割されたタスクがどのように動作し、いくつのスレッドが例外をスローする可能性があり、これを報告する必要があるかでわかります。あなたがこれからどのように恩恵を受けるかもしれないかの愚かな例がここにあります:
public void Run()
{
try
{
this.SillyMethod(1, 2);
}
catch (AggregateException ex)
{
Console.WriteLine(ex.Message);
foreach (Exception innerException in ex.InnerExceptions)
{
Console.WriteLine(innerException.Message);
}
}
}
private void SillyMethod(int input1, int input2)
{
var exceptions = new List<Exception>();
if (input1 == 1)
{
exceptions.Add(new ArgumentException("I do not like ones"));
}
if (input2 == 2)
{
exceptions.Add(new ArgumentException("I do not like twos"));
}
if (exceptions.Any())
{
throw new AggregateException("Funny stuff happended during execution", exceptions);
}
}
例外のネストとキャッチブロックの試行
1つの例外/ try
catch
ブロックをもう1つの中に入れ子にすることができます。
このようにして、全体の仕組みを乱すことなく作業できる小さなコードブロックを管理できます。
try
{
//some code here
try
{
//some thing which throws an exception. For Eg : divide by 0
}
catch (DivideByZeroException dzEx)
{
//handle here only this exception
//throw from here will be passed on to the parent catch block
}
finally
{
//any thing to do after it is done.
}
//resume from here & proceed as normal;
}
catch(Exception e)
{
//handle here
}
注意:親キャッチブロックに投げるときに例外を飲み込まないようにしてください
ベストプラクティス
カンニングペーパー
行う | しないでください |
---|---|
制御文による制御フロー | 例外を含む制御フロー |
ロギングによって無視された(吸収された)例外を追跡する | 無視する例外 |
throw を使用して例外を繰り返します。 | 例外を再スローする - throw new ArgumentNullException() throw ex か、またはthrow ex |
定義済みのシステム例外をスローする | 事前定義のシステム例外と同様のカスタム例外を投げる |
アプリケーションロジックにとって重要な場合は、カスタム/事前定義された例外をスローする | カスタム/事前定義された例外をスローして、フローに警告を表示する |
処理したい例外をキャッチする | すべての例外をキャッチ |
例外を使用してビジネスロジックを管理しないでください。
フロー制御は例外によって実行すべきではありません。代わりに条件文を使用してください。コントロールがif-else
ステートメントではっきりと実行できる場合は、可読性とパフォーマンスが低下するため、例外を使用しないでください。
Mr. Bad Practicesの次のスニペットを考えてみましょう。
// This is a snippet example for DO NOT
object myObject;
void DoingSomethingWithMyObject()
{
Console.WriteLine(myObject.ToString());
}
実行がConsole.WriteLine(myObject.ToString());
到達したときConsole.WriteLine(myObject.ToString());
アプリケーションはNullReferenceExceptionをスローします。氏バッドプラクティスことに気づいmyObject
nullで、キャッチ&処理するために彼のスニペットを編集したNullReferenceException
:
// This is a snippet example for DO NOT
object myObject;
void DoingSomethingWithMyObject()
{
try
{
Console.WriteLine(myObject.ToString());
}
catch(NullReferenceException ex)
{
// Hmmm, if I create a new instance of object and assign it to myObject:
myObject = new object();
// Nice, now I can continue to work with myObject
DoSomethingElseWithMyObject();
}
}
以前のスニペットは例外のロジックしかカバーしていないので、 myObject
がこの時点でnullでない場合はどうすればよいですか?ロジックのこの部分はどこでカバーする必要がありますか? Console.WriteLine(myObject.ToString());
直後Console.WriteLine(myObject.ToString());
? try...catch
ブロックの後はどうですか?
ベストプラクティスはどうですか?彼はこれをどのように扱いますか?
// This is a snippet example for DO
object myObject;
void DoingSomethingWithMyObject()
{
if(myObject == null)
myObject = new object();
// When execution reaches this point, we are sure that myObject is not null
DoSomethingElseWithMyObject();
}
ベストプラクティスは、より少ないコードと明確かつ分かりやすいロジックで同じロジックを実現しました。
例外を再スローしないでください
例外の再投げは高価です。パフォーマンスに悪影響を与えます。日常的に失敗するコードでは、デザインパターンを使用してパフォーマンスの問題を最小限に抑えることができます。 このトピックでは、例外がパフォーマンスに重大な影響を与える場合に役立つ2つの設計パターンについて説明します。
ロギングなしで例外を吸収しない
try
{
//Some code that might throw an exception
}
catch(Exception ex)
{
//empty catch block, bad practice
}
例外を飲み込まないでください。例外を無視すると、その瞬間が省かれますが、後で保守性のために混乱が生じます。例外を記録するときは、完全なスタックトレースが記録され、例外メッセージのみが記録されるように例外インスタンスを記録する必要があります。
try
{
//Some code that might throw an exception
}
catch(NullException ex)
{
LogManager.Log(ex.ToString());
}
あなたが処理できない例外をキャッチしないでください
このような多くのリソースは、なぜあなたがそれをキャッチしている場所で例外をキャッチしているのかを強く主張します。その場所で処理できるのであれば、例外をキャッチする必要があります。別のアルゴリズムを試したり、バックアップデータベースに接続したり、別のファイル名を試したり、30秒待ってからもう一度やり直したり、管理者に通知したりするなど、問題を緩和するために何か問題が発生した場合は、エラーをキャッチしてそのことを行うことができます。もしあなたが妥当かつ合理的に行うことができるものがなければ、単に "放置して"例外をより高いレベルで処理させてください。例外が十分に致命的で、問題の重大度のためにプログラム全体がクラッシュする以外の合理的な選択肢がない場合は、クラッシュさせてください。
try
{
//Try to save the data to the main database.
}
catch(SqlException ex)
{
//Try to save the data to the alternative database.
}
//If anything other than a SqlException is thrown, there is nothing we can do here. Let the exception bubble up to a level where it can be handled.
未処理スレッド例外
AppDomain.UnhandledExceptionこのイベントは、キャッチされていない例外の通知を提供します。システムデフォルトハンドラが例外をユーザに報告して終了する前に、アプリケーションが例外に関する情報を記録できるようにします。アプリケーションの状態に関する十分な情報が利用可能な場合は、後で復旧するためにプログラムデータを保存するなどの措置が取られる可能性があります。例外が処理されないとプログラムデータが破損する可能性があるため、注意が必要です。
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
private static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledException);
}
Application.ThreadExceptionこのイベントにより、Windowsフォームアプリケーションで、Windowsフォームスレッドで発生する未処理の例外を処理できます。イベントハンドラをThreadExceptionイベントにアタッチして、これらの例外を処理します。これにより、アプリケーションは不明な状態になります。可能であれば、例外は構造化された例外処理ブロックで処理する必要があります。
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
private static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledException);
Application.ThreadException += new ThreadExceptionEventHandler(ThreadException);
}
最後に例外処理
static void UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Exception ex = (Exception)e.ExceptionObject;
// your code
}
static void ThreadException(object sender, ThreadExceptionEventArgs e)
{
Exception ex = e.Exception;
// your code
}
例外のスロー
あなたのコードは、何か異常が起きたときに例外をスローすることができます。
public void WalkInto(Destination destination)
{
if (destination.Name == "Mordor")
{
throw new InvalidOperationException("One does not simply walk into Mordor.");
}
// ... Implement your normal walking code here.
}