potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

C# HRESULTパターン、例外パターン、Nullableパターンのどれを使うかで悩む

C#で自分用のクラスライブラリを作るとき、最中にもエラー対処パターンで悩みます。COM開発で使えるデザインパターンはおそらくHRESULTパターン、例外パターン、Nullableパターン(適当に名付けました)なのですが、一長一短です。

  • HRESULTパターン
    • エラーを軽く素早く具体的に伝えられます。3つの中でC環境でも使えるのはこれだけです。ただし、毎回HRESULTをチェックするのでC#のプロパティとは親和性が低く、outと組み合わせてもコードが一行長くなり可読性が少々犠牲になります。一方、昨今はoutと組み合わせればRAIIも対応できる強みもあり、他のパターンを使う場合もP/Invokeで多用します。
  • 例外パターン
    • C++でも使われてきた安定の方法です。プロパティとの親和性も高く、想定外の動作は即座に中断されるのでバグに気づきやすいです。また、行番号などが記載されるのでデバッグも容易です。弱点は例外発生時のコストが他よりも大きく、特定のエラーコードが想定される場合もtry構文が必要で冗長になることでしょうか。そこだけ他のパターンにしても良いですが、一貫性は崩れます。一方、次のNullableパターンを使う場合もメモリ確保エラーなどの想定外のミスではお世話になります。
  • Nullableパターン
    • C++でもC#でもここ数年で本格導入されつつあるパターンだと思います。C++ならstd::optional (C++20)やstd::expected (C++23予定?)、C#ならNullable<T>T?を使います。従来はポインタでしか表現できなかったnullptr or nullを標準ライブラリの機能で値型にも提供するパターンです。メモリや速度のコストは少々増えますが、C++なら三項演算子や今後導入されるモナドC#ならパターンマッチングと極めて相性が良いです。ただし、C#ではnullptrが素通りしてしまう$"{...}"のような場合がしばしばあってバグの原因となりやすい印象があります。また、現状のC#ではC++std::expectedのようなメモリ消費の考慮された標準機能がないので具体的なエラーを把握しにくくなります。

WILのように複数のパターンを網羅してしまう(WILはHRESULT、例外、フェイルファスト)のもありだと思いますが、最適化でコードが消えない気がするC#ではすべてを準備するのはよろしくない気がします(最適化されていたらすみません)。ひとまずNullableパターンで落ち着こうと思いますが、まだまだ悩みながらになりそうです。