堅牢なソフトウェアを書くときに、再実行可能な操作を実行することがある。システムを堅牢にするために、各操作は以前の操作状況から独立しているようにコード化できる。具体例として、ファイル処理パイプラインを考えてみよう。
パイプラインの最初のステップは、ファイルサーバーをポーリングして、新しく検出されたファイルをダウンロードする。次のステップは、それらのファイルを解析して、より使いやすいフォーマットに変換する。3番目のステップは、その後の処理のために変換されたファイルをデータベースにインポートする。
最初のステップにおいて、いくつかのファイルをダウンロードした後で失敗した場合、アプリケーション全体が中断されるべきではない。代わりに、ダウンロードに成功したファイルを解析する次のステップに進むべきである。失敗したファイルは次のサイクルで拾えばよいので、これは許容できる。
このパターンをC#で実装するには、通常、一連のtry-catchブロックを使用する。
try
{
DownloadFiles();
}
catch (Exception ex)
{
//log errors
}
try
{
ParseAndConvertFiles();
}
catch (Exception ex)
{
//log errors
}
try
{
ImportFiles();
}
catch (Exception ex)
{
//log errors
}
Deferredエラーハンドリング提案では、多くの定型コードを削除できる。
#exception mode deferred
DownloadFiles();
ParseAndConvertFiles();
ImportFiles();
if (Exception.LastException != null)
{
//log errors
Exception.ClearLastException();
}
#exception mode structured
deferredエラーハンドリングを使うために、「例外モード(exception mode)」と呼ばれる新しいコンパイラディレクティブが使われる。これにより、現在の関数において、構造化例外処理と新しいdeferredモードの間が切り替わる。
deferredモードでは、エラーが発生したかどうかを判定するためにException.LastExceptionプロパティが使われる。これは最新のエラーのみを格納するため、複数のエラーが発生した場合、最後のエラー以外は失われる。
これは各行のあとでLastExceptionをチェックする必要があることを意味するため、必要なコード量を減らすという目標とは逆で、いくつかの懸念が生じる。
これを解決するための修正案として、LastExceptionをスタックで置き換えることが提案されている。これにより、開発者はすべての例外を発生した時系列の逆順に確認できる。
検討されているもう一つのオプションは、ジャンプ先を指定できるものである。
#exception mode deferred error_handler
[...]
return;
error_handler:
//log error
#exception resume next
「next」修飾子を省略すると、アプリケーションは次の行に進むのではなく、失敗したステートメントを再試行する。
構造化とdeferredエラーハンドリングの両方を同じ関数で同時に使った場合、コンパイラの観点から問題が発生する可能性がある。Deferredモードは、CLRサポートを受けることなく、C#がクロージャーとasync/awaitを実装し、コンパイルされたコードを根本的に変更する。コンパイラに必要な変更を単純化するために、他の構文が検討されている。これはunsafeとcheckedブロックで見るものと同じタイプの構文である。
deferred
{
DownloadFiles();
ParseAndConvertFiles();
ImportFiles();
}
変更の規模が大きいため、この提案がC# 9よりも前に採用される可能性は低い。