厄介なバグに繋がる可能性があるコード
class Test { public class Hoge : System.Exception {} }
(1) class Test {
(2)
(3) public class Hoge(4) : System.Exception { (5) }
}
(1) public にする。
(2) [Serializable] を指定する。
(3) sealed を付ける。
(4) 名前の末尾に Exception を付ける。
(5) 2種類のコンストラクタが必要(後述)。
↓
修正後のコード
public class Test { [Serializable] sealed public class HogeException : System.Exception { public HogeException() : base() {} HogeException(SerializationInfo info, StreamingContext context) : base(info, context) {} } }
こんなの知らないと一生気づかないです。
SonarLint の警告で気づくことができました。
System.Exception が ISerializable を継承しているので、カスタムの例外を作る場合は ISerializable のガイドラインに従うことが推奨されます。
といっても、Microsoft 公式の情報はしっちゃかめっちゃかで、把握しづらいです。
Bing AI がそれっぽい回答をくれました。
「例外のシリアライズについて教えてください。」
例外のシリアライズとは、例外オブジェクトをバイトストリームに変換することを指します。これにより、例外オブジェクトを別のプロセスやマシンに転送し、そこで逆シリアル化して再構築することができます。例えば、リモートプロシージャ呼び出し(RPC)などの分散アプリケーションで使用されます。 .NETでは、System.ExceptionクラスはSystem.Runtime.Serialization.ISerializableインターフェースを実装しており、カスタムの例外クラスもこのインターフェースを実装することができます。このインターフェースには、GetObjectDataメソッドが定義されており、このメソッドをオーバーライドして、シリアル化するデータを指定することができます。また、逆シリアル化時には、特殊なコンストラクター(シリアル化コンストラクター)が呼び出されます。このコンストラクターは、SerializationInfoとStreamingContextの2つの引数を取ります。 カスタムの例外クラスに独自のデータメンバーがある場合は、それらのメンバーをシリアル化および逆シリアル化するために、上記のメソッドおよびコンストラクターを適切に実装する必要があります。
「C#でカスタム例外を作成する方法はありますか?」
はい、C#でカスタム例外を作成することができます。カスタム例外を作成するには、System.Exceptionクラスを継承し、必要に応じて独自のプロパティやメソッドを追加します。また、基本クラスのコンストラクターをオーバーライドして、独自のコンストラクターを定義することもできます。
以下は、簡単なカスタム例外クラスの例です。
public class MyCustomException : Exception { public MyCustomException() { } public MyCustomException(string message) : base(message) { } public MyCustomException(string message, Exception inner) : base(message, inner) { } }
この例では、MyCustomExceptionクラスがSystem.Exceptionクラスから派生しており、3つのコンストラクターが定義されています。これらのコンストラクターは、基本クラスの対応するコンストラクターを呼び出しています。このようにして、カスタム例外クラスを作成することができます。
↑のように対応しておけば十分なのかも知れませんが、SonarLint のチェックはもっと厳しいです。
- [Serializable] は必ず付けなければならない。
- 派生クラスがないなら sealed を付けなければならない。
- string を引数にとるコンストラクタは使わないなら不要。
- string と Exception を引数にとるコンストラクタも同様。
- SerializationInfo と StreamingContext を引数にとるコンストラクタが必要。
※このコンストラクタは明示的に呼ばないなら private でも構わない。
コンパイラ側でうまいことやって欲しいです。
やることが分かっている&必要な状況も分かっているので、使う側が手間かけないとならないのが面倒。
このルールがあるせいで、カスタム例外を気軽に実装できないので、無理やり既存の例外で済ませてしまい、例外がスローされたときに意味が分からなくて調査に時間がかかる…ということが頻発しそう…。
使いそうな例外はどこかにまとめておく…という整備が必要になりそう…。
チームの規模が大きくなるほど、放置すると収集がつかなくなりそう。
逆にシリアライズとデシリアライズを行わないことが明確なら、気にする必要はないということですが、「今は必要ないから対応しないでいいや」…と思って放置しておくと、別のエンジニアの対応で状況が変わり、シリアライズ/デシリアライズのタイミングで原因不明のクラッシュが発生する…ということに繋がりそう。
公式のドキュメント(英語)
「【.Net C#】System.Exception を継承すると ISerializable がどうのこうのと警告が出る」への1件のフィードバック