というのを、この数日をかけて勢いで作りました。ずーーっと昔から「enum
は遅い」と言われ続けていたので何か手を入れたいと思っていたのですが、突然やる気になりました。久々にプライベートのコーディング意欲が爆上げしたー。勢いが強過ぎて毎日寝不足のまま明け方までのコーディングを繰り返し、しかも家庭を顧みませんでした...(超ヨクナイ
どのくらい速いかというと、.NET Core 3.0 よりも何倍も速いです。もう狂ったくらい速い。全てのメソッドがゼロ・アロケーションです。プログラミングする上で enum
はかなり使うので、多少なり性能改善に寄与できるのではないかと思います。ぜひ使ってみた感想をいただければなーと思います :)
簡単に使い方解説
.NET 標準の System.Enum
に近い使い方ができるように API を設計しています。以下のような感じで、とてもシンプル!
//--- FastEnum var values = FastEnum<Fruits>.Values; var names = FastEnum<Fruits>.Names; var name = Fruits.Apple.ToName(); var defined = FastEnum<Fruits>.IsDefined(123); var parse = FastEnum<Fruits>.Parse("Apple"); var tryParse = FastEnum<Fruits>.TryParse("Apple", out var value);
//--- .NET var values = Enum.GetValues(typeof(Fruits)) as Fruits[]; var names = Enum.GetNames(typeof(Fruits)); var name = Enum.GetName(typeof(Fruits), Fruits.Apple); var defined = Enum.IsDefined(typeof(Fruits), 123); var parse = Enum.Parse<Fruits>("Apple"); var tryParse = Enum.TryParse<Fruits>("Apple", out var value);
おまけ機能
enum
周りでよく使う機能があって、それらも一緒に組み込むことでより便利に使えるようにしています。
1. メンバー情報を丸ッと取得
たまに enum
の値と名前のペアが欲しくなることがあります。そういうケースに耐えるために Member<TEnum>
というのを用意しています。FieldInfo
なども一緒に入っているので、リフレクションなどのお供にご利用ください。
class Member<TEnum> { public TEnum Value { get; } public string Name { get; } public FieldInfo FieldInfo { get; } // etc... } //--- こんな感じで取得できるので、煮るなり焼くなり var member = Fruits.Apple.ToMember();
2. EnumMemberAttribute.Value
の取得
EnumMemberAttribute
をフィールド名の別名として利用している方をちょくちょく見かけます。このケースに対応するため、Value
プロパティからサッと値を取得できるようにしました。
enum Company { [EnumMember(Value = "Apple, Inc.")] Apple = 0, } var value = Company.Apple.GetEnumMemberValue(); // Apple, Inc.
3. 複数のラベルを付与
EnumMemberAttribute
は AllowMultiple = false
になっているため、同一のフィールドに対して複数の属性を付けられません。それが不便で個人的には好んでおらず、その代替として LabelAttribute
というのを作って使っています。FastEnum にこの機能を追加することで、以下のような感じで便利に利用できます。
enum Company { [Label("Apple, Inc.")] [Label("AAPL", 1)] Apple = 0, } var x1 = Company.Apple.GetLabel(); // Apple, Inc. var x2 = Company.Apple.GetLabel(1); // AAPL
制限
System.Enum
に対して完全に互換があるかというと、実はそうではないです。標準で提供されているいくつかの機能を削ることで速さを得ている部分があります。
1. Generics な API のみを提供
標準の System.Enum
は typeof(TEnum)
を用いたオーバーロードが用意されているのですが、その場合、引数か戻り値が object
になります。そこで box 化が走って非常に遅くなってしまうので、機能として削ることで速度を稼ぐようにしています。
2. カンマ区切りの文字列を Parse
できない
実は System.Enum.Parse
は以下のような感じの文字も parse できます。あまり知られていないんじゃないかと思うくらい、ひっそりと存在する仕様です。
//--- こんなのがあるとして [Flags] enum Fruits { Apple = 1, Lemon = 2, Melon = 4, Banana = 8, } //--- カンマ区切りの文字列を与える var value = Enum.Parse<Fruits>("Apple, Melon"); Console.WriteLine((int)value); // 5
フラグ処理をするときに便利な機能のようですが、これまで 10 年以上一度も使ったことがありませんでした。このカンマ区切りの解析を加えようとするとそのオーバーヘッドが出てしまうので、思い切って機能を削ることで速度を稼いでいます。ほとんど使われない (少なくとも自分は使わない) 機能のために速度を落とすのが嫌だったのでそうしています。そのあたりは本家に譲ればいいかな、と...。
速さの秘訣
言わずもがなでお察しかもしれませんが、内部でキャッシュしているからです。Static Type Caching というアプローチを取っていて、そのおかげで読み込み速度が限りなく 0 になっています*1。これを基礎としつつ、アロケーション回避のためのテクニックを交えたり、内部で利用する辞書を特定のキーに特化させるなどしています*2。
このあたりのテクニックはのいえ先生 (@neuecc) の講演スライドや Cysharp 印の GitHub リポジトリが大変参考になります。