C# の列挙型がインターフェイスを実装できないのが不便


C# の列挙型は Java のそれとは異なりインターフェイスを実装することはできません。普段はあまり困ることはないのですが,困ることもあるのです。

ゲノミクスではしばしば FASTA 形式 のファイルを扱います。 FASTA 形式は核酸配列もしくはアミノ酸配列を記述したものです。

核酸やアミノ酸は限られた種類しかないので列挙型で定義するのが良いです。列挙型では switch や論理演算が行えるのが列挙型の強みです。特に FASTA 形式では N (任意の塩基) とか Z (グルタミンもしくはグルタミン酸) のようなものが出てくるので,後者が使えるのは非常に便利なのです。

public interface IAcid
{
}

[Flags]
public enum Dna // : IAcid
{
	A = 1, G = 2, C = 4, T = 8,
	N = A | G | C | T,
}

[Flags]
public enum AminoAcid // : IAcid
{
	E = 1, Q = 2,
	Z = E | Q,
}

さて,ここで「配列」という抽象クラス Sequence を考えます。 SequenceDna もしくはアミノ酸を保持するものですから,実際に使うときはジェネリックに Sequence<Dna>Sequence<AminoAcid> のように使いたいです。つまり次のような定義を行いたいわけです。

public class Sequence // where TAcid : IAcid
{
}

しかしこのままでは TAcid に制約がないため, TAcid に任意の型を代入できてしまいます。しかし,特定の列挙型 DnaAminoAcid だけを許可する方法がありません。せいぜい where TAcid : struct くらいです。

これを解決する方法はいくつかあると思います。

  1. 列挙型ではなくクラスにする (おそらく Singleton パターンを使う)。
  2. ジェネリックをやめて各列挙型に対応する Sequence のサブクラスを作成する。
  3. ジェネリックをやめて実行時に型チェックする。

最初の方法がおそらく最良の方法だと思います。ただし switch が使えなくなるのと,演算子のオーバーロードやら Enum で使えるようなメソッドを実装するのが非常に面倒です。

2 つ目の方法はクラスの関係が複雑になります。ただ今回の例のような場合では,せいぜい DNA, RNA,アミノ酸の 3 種類位しか出てこないので,管理できないまでにはならないでしょう。

最後の方法は非常に実装は簡単ですが,文明を遡る愚行でしょう。実装自体は簡単でも,いざそれを使用する際に実行時のエラーチェックが大変になると思います。書き捨てるタイプのものなら良いかもしれませんが,後々使うものなら避けた方が賢明かと思われます。

もしかしたら上に述べた以外にスマートな解決方法があるのかもしれません。 C# 3.0 からは拡張メソッドで列挙型もメソッドを擬似的に使うことができるのだから,列挙型がインターフェイスを実装できても良い気がするのですが。もしくは拡張インターフェイスみたいな。