# カスタム例外の作成
他の例外と同様にスローできるカスタム例外を実装できます。これは、実行時に例外を他のエラーと区別できるようにしたい場合に有効です。
この例では、アプリケーションが複雑な入力を解析する際に発生する可能性がある問題を明確に処理するために、カスタム例外を作成します。
# カスタム例外クラスの作成
カスタム例外を作成するには、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)
例外処理を微調整するためのセマンティクスが追加されます。
カスタム クラスは、追加のシナリオをサポートするために次の機能を実装できます。
# 再スロー
解析プロセス中は、元の例外がまだ重要です。この例では 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
{
/* 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
block は、ファイルから読み取るときに非常に便利です。
例:
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
ブロック。ただし、catch ブロックがないため、実行すると終了します。終了前に、finally ブロック内のステートメントが実行されます。
ファイルの読み取りでは、 using
を使用できました FileStream
としてブロック (何 OpenRead
戻ります) IDisposable
を実装します .
return
があっても try
のステートメント ブロック、finally
ブロックは通常実行されます。そうでない場合がいくつかあります:
- StackOverflow が発生したとき
Environment.FailFast
- アプリケーション プロセスは通常、外部ソースによって強制終了されます。
# ベスト プラクティス
# チートシート
DO | しない |
---|---|
制御ステートメントによる制御フロー | 例外のある制御フロー |
無視された (吸収された) 例外をログに記録して追跡する | 例外を無視 |
throw を使用して例外を繰り返します | 例外を再スロー - throw new ArgumentNullException() または 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());
に達したとき アプリケーションは NullReferenceException をスローします。 Mr. Bad Practices は 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());
の直後 ? 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();
}
Mr. Best Practices は、より少ないコードと明確でわかりやすいロジックで同じロジックを実現しました。
# 例外を再スローしない
例外の再スローはコストがかかります。パフォーマンスに悪影響を及ぼします。定期的に失敗するコードについては、デザイン パターンを使用してパフォーマンスの問題を最小限に抑えることができます。このトピックでは、例外がパフォーマンスに大きな影響を与える可能性がある場合に役立つ 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.
# 例外アンチパターン
# 嚥下例外
次の方法で常に例外を再スローする必要があります:
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.
}
# 基本的な例外処理
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 つの catch ブロックのみが実行されます。
# 集約例外 / 1 つのメソッドからの複数の例外
1 つのメソッドで複数の例外をスローすることはできないと誰が言いますか。 AggregateExceptions をいじることに慣れていない場合は、独自のデータ構造を作成して、多くの問題を表現したくなるかもしれません。もちろん、検証の結果など、より理想的な例外ではない別のデータ構造がありました。 AggregateExceptions をいじっても、受信側にいて、それらが役に立つことに気づかずに常に処理している可能性があります。
メソッドを実行することは非常にありそうであり、全体としては失敗しますが、スローされた例外で問題が発生した複数の事柄を強調表示する必要があります。例として、この動作は、Parallel メソッドがどのように機能するかで見ることができます。タスクは複数のスレッドに分割され、任意の数のスレッドが例外をスローする可能性があり、これを報告する必要があります。これがどのように役立つかを示すばかげた例を次に示します:
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);
}
}
# 例外のスロー
何か異常が発生した場合、コードは例外をスローできますし、多くの場合そうすべきです。
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.
}
# 未処理およびスレッド例外
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
}
# 例外オブジェクトの使用
独自のコードで例外を作成してスローすることができます。例外のインスタンス化は、他の 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
}
注: catch ブロック内で新しい例外をスローする場合は、元の例外が「内部例外」として渡されていることを確認してください。たとえば、
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:0 であるため、b で除算できません ---> System.DivideByZeroException:ゼロで除算しようとしました。
C:[...]\LINQPadQuery.cs:line 36 の UserQuery.g__DoSomething0_0() で
--- 内部例外スタック トレースの終了 ---
C:[...]\LINQPadQuery.cs:line 42 の UserQuery.g__DoSomething0_0() で
C:[...]\LINQPadQuery.cs:line 55 の UserQuery.Main() で
この例を LinqPad で試してみると、行番号があまり意味をなさないことに気付くでしょう (必ずしも役立つとは限りません)。しかし、上記のように役立つエラー テキストを渡すと、多くの場合、エラーの場所を突き止める時間が大幅に短縮されます。この例では、明らかに行です
c =a / b;
関数内 DoSomething()
.
.NET Fiddle で試す
# 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://www.brainthud.com/cards/5218/25441/which-four-behavior-interfaces-exist-for-interacting-with-a-service-or-client-description-what-methods-do-they-実装と
その他の例:
HTTP ステータス コードが 401 Unauthorized の場合、IErrorHandler が間違ったメッセージ本文を返す
IErrorHandler が WCF でエラーを処理していないようです。何かアイデアはありますか?
カスタムWCFエラーハンドラーがOK以外のhttpコードでJSON応答を返すようにする方法は?
HttpClient リクエストの Content-Type ヘッダーをどのように設定しますか?
# 例外のネストと try catch ブロック。
1 つの例外をネストできます / try
catch
このようにして、メカニズム全体を中断することなく機能する小さなコード ブロックを管理できます。
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
}
注: 親の catch ブロックにスローするときに例外を飲み込まないようにする