コンパイル時にエラーを検出する
というのはあたりまえのようで結構重要だったりして、よく書籍でも取り上げられている。
たとえば、かなり適当かつ実用性は疑問だが、
public int NormalizeAngle(int degrees) { if (degrees < 0) degrees = 360 + degrees % 360; return degrees % 360; } public double NormalizeAngle(double radian) { if (double.IsNaN(radian) || double.IsInfinity(radian)) { return radian; } /* 略 */ return aPI; }
のようなメソッドがあるとして、利用する側が float で角度を保持していたら、暗黙の変換が発生して double 型のメソッドが呼び出されることになり、実行時に期待しない数値になってしまうかもしれない。これを防ぐために例えば
public float NormalizeAngle(float unknown) { throw new ApplicationException("unrecognized unit"); }
のようなメソッドを足せば、実行時に例外という形で通知される。しかし、このままでは実際に実行時に通るまでプログラムミスを検出できないため、検出が遅れ修正にコストが掛かるほか、最悪リリースまで検出されない可能性だってある。
本来の用途とは異なるが、コンパイル時にエラーを通知するために ObsoleteAttribute を使うことができる。*1
// float での呼び出しはコンパイルエラーとする [Obsolete("MUST cast explicity to Int32 or Double", true)] public float NormalizeAngle(float unknown) { throw new ApplicationException("unrecognized unit"); } or // float での呼び出しは degrees とみなして丸めるとして警告する [Obsolete("Single implicit convert to Int32", false)] public float NormalizeAngle(float maybe) { return NormalizeAngle(Convert.ToInt32(maybe)); }
似たようなパターンとして、可変長引数 ParamArrayAttribute を利便性のために付与する場合なんかにも利用できる。
public float Ave(params int[] values) { int sum = Sum(values); return (float) sum / values.Length; }
C# の場合、可変長引数が完全省略された場合は長さ0の配列が引数が割り当てられるが、この場合は0除算になってしまうし、.NET 対応プログラム言語がすべて可変長引数をサポートしているわけではないので、values には null がありうる。
とりあえず、null がやってくる場合は置いといて、引数が最低1個以上あることを
public float Ave(int first, params int[] values) { int sum = first + Sum(values); return sum / (values.Length + 1F); }
などとして表現することもできるが、Sum() の呼び出しに不満が残ってしまう*2ので、ObsoleteAttribute を利用して
public float Ave(params int[] values) { if (values == null) throw new ArgumentNullException("values", "MUST need 1 or more values"); int sum = Sum(values); return (float) sum / values.Length; } [Obsolete("MUST need 1 or more values", true)] [EditorBrowsable(EditorBrowsableState.Never)] public float Ave() { // 実行時に呼び出された時の動作を同一にする return Ave((int[]) null); }
こんな感じにしておけば、引数が0個の場合をエラーにしたり警告にすることができる。*3
実行時に呼び出された時の動作を引数0個で Ave(int[]) が呼び出されたときと同一にするため、Ave() を呼び出しているが、int 型の場合は null と互換性がないので上記のような型変換を記述する必要はない。しかし null と互換性のある型が引数になっていると new PARAMS[1] { null } として扱われてしまうので型変換を記述する必要がある。
また、上の例では EditorBrowsableAttribute を付与し、Visual Studio .NET などのコード補間機能に選択肢として登場しないようにしている。こうしておくことで、このメソッドを利用するときに引数なしのオーバーロードがあることは隠され、引数を0個でうっかり呼び出してしまった場合だけにエラーを表示するようになる。*4