#何を言い出すのか
enum、便利ですよね。入力値の制限ができますし、入力補完もききます。が、一つ問題があります。オリジナルを弄らずに値を追加することができないことです。
###例えば、
ゲームか何かを作っていたとしてキャラクターの一つとして、現場猫を出すとしましょう。現場猫もいろんな種類がいます。定番の「ヨシ!」というやつ、受話器を持って「どうして」と悲嘆な表情なやつ、などなど。
我々の鬱憤や不満などそういった機微を混ぜこぜにして新しい現場猫を作りたくなるかも知れません。仮に適当なことを言うSEにでもしましょうか。ちょっと突っ込んだ質問をすると「まぁそれで良いんじゃないかと思います」とクライアントにロクに確認も取らずに生返事をして後々トラブルになりそうなアレです。警戒心ヤベェです。
その時、現場猫の種類をenumで管理してたとして、新しい現場猫を追加するとなれば、enumの宣言をどうすれば良いでしょうか。
public enum GenbaCatType
{
NoProblem, //ヨシ!
Why //どうして
}
public enum ExtendedGenbaCatType : GenbaCatType
{
ItsOkIThink //まぁそれで良いんじゃないかと思います
}
少なくとも今(2019/11)はこんなことは出来ないです。
#じゃぁclassにしてconstメンバだね
そうだね。
public class GenbaCatType
{
public const int NoProblem = 0; //ヨシ!
public const int Why = 1; //どうして
}
public class ExtendedGenbaCatType : GenbaCatType
{
public const int ItsOkIThink = 2; //まぁそれで良いんじゃないかと思います
}
まぁできなくはないのですが、いざ使うとなるとintで宣言しなければならず、何を表すか不明瞭です。加えて、intなので数字をフリーダムに入れることができます。できるなら避けたいですね。
#じゃぁ、どないするのか
こんな感じで書いてみました。
using System;
class Program
{
public class GenbaCatType
{
public static readonly GenbaCatType NoProblem = new GenbaCatType(0); //ヨシ!
public static readonly GenbaCatType Why = new GenbaCatType(1); //どうして?
private readonly int _type;
private GenbaCatType()
{
}
protected GenbaCatType(int type)
{
_type = type;
}
private bool Equals(GenbaCatType other)
{
return GetHashCode() == other.GetHashCode();
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((GenbaCatType) obj);
}
public override int GetHashCode()
{
return _type;
}
public static bool operator ==(GenbaCatType cat1, GenbaCatType cat2)
{
if (ReferenceEquals(cat1, null) && ReferenceEquals(cat2, null)) return true;
if (ReferenceEquals(cat1, null)) return false;
if (ReferenceEquals(cat2, null)) return false;
return cat1.GetHashCode() == cat2.GetHashCode();
}
public static bool operator !=(GenbaCatType cat1, GenbaCatType cat2) => !(cat1 == cat2);
public static GenbaCatType Create(int hashCode) => new GenbaCatType(hashCode);
}
public class ExtendedGenbaCatType : GenbaCatType
{
public static readonly GenbaCatType ItsOkIThink = new ExtendedGenbaCatType(2); //それで大丈夫だと思います。
private ExtendedGenbaCatType(int type) : base(type)
{
}
}
static void Main(string[] args)
{
GenbaCatType catNoProblem = ExtendedGenbaCatType.NoProblem;
GenbaCatType catOk = ExtendedGenbaCatType.ItsOkIThink;
//GenbaCatType dog = new GenbaCatType(); //できません
GenbaCatType monkey; //これはできちゃう(中身はnull)
Console.WriteLine(catNoProblem == catOk); //False
Console.WriteLine(catNoProblem == GenbaCatType.NoProblem); //True
}
}
上述のconstメンバからもう少し話を進めて、intからそのクラス型に制限、想定していないtypeの実体を作れないようコンストラクタをprotectedで隠蔽、比較演算子も実装して、表面上はenumのように使えるようにしました。入力補完もきいてくれるので打ち込むときも楽です。セーブ・ロードなどのシリアライズが必要になった時に備えてHashでの入出力も備えました。
難点としてはnullを持てちゃうこと、そしてenumに比べて宣言がクッソ面倒臭いことです。