前振り
前回、高速だけれど、あまりにも面倒くさ過ぎるEnumイディオム、CEnumを紹介しました。
今回は、その面倒くさい部分をSource Generatorsで自動生成しつつ、色々と便利にしてみました。
ソースコードは、ここにあります。
本題
使い方
usingを定義します。
using CEnum;
enumの前に[CEnum]属性を付けます。
[CEnum] enum ETest {a,b,}
Flags属性とEnumMember属性にも対応しています。
[CEnum, Flags] enum EFlag {[EnumMember(Value="A")]a=(1<<0),[EnumMember(Value="B")]b=(1<<1),}
enumからCEnumへの変換は、以下の通りです。
var c0 = (CETest)ETest.a;
CETest c1 = ETest.b;
CEnumからenumへの変換は、以下の通りです。
var e0 = (ETest)c0;
Etest e1 = c1;
enumから変換することなく、CEnumで完結させた方が、高速且つ、安全です。
var c2 = CETest.Ca; // enum名とメンバー名に、Cプリフィクスを付けます
var c3 = CETest.Cb; // enum名とメンバー名に、Cプリフィクスを付けます(大事なことなので、2度書きました)
switchは、このように書けますが、
switch (c2){
case (int)ETest.a: break;
case (int)ETest.b: break;
}
Valueを引いた方が高速です。
switch (c2.Value){
case (int)ETest.a: break;
case (int)ETest.b: break;
}
CEnumは、Lengthを簡単に引けるので便利です。
var n = CETest.Length;
例えば、配列の確保を、このように短く書けます。
var a0 = new string[CETest.Length];
a0[(int)ETest.a] = "a";
a0[(int)ETest.b] = "b";
var s0 = a0[c2.Value];
Flags属性が付いていれば、論理演算が可能になります。
var c4 = CEFlag.Ca;
var c5 = c4 | CEFlag.Cb;
HasFlag()以外にも、便利なメソッドが追加されます。
bool AllFlag(CEnum<T> c); // ((this.Value & c.Value) == c.Value) HasFlag()と同等
bool AnyFlag(CEnum<T> c); // ((this.Value & c.Value) != default)
bool NotFlag(CEnum<T> c); // ((this.Value & c.Value) == default)
bool OtherAllFlag(CEnum<T> c); // ((this.Value & ~c.Value) == ~c.Value)
bool OtherAnyFlag(CEnum<T> c); // ((this.Value & ~c.Value) != default)
bool OtherNotFlag(CEnum<T> c); // ((this.Value & ~c.Value) == default)
EnumMemberのValueを引くことも出来ます。
var m4 = c4.GetEnumMemberValue();
そして最後に、classの中で定義したenumに[CEnum]を付ける場合、そのclassをpartialにする必要がありますので、ご注意ください。
partial class Test
{
[CEnum] enum ETest {a,b,}
}
特徴
「列挙型変数の高速化」が、CEnumの主目的です。
標準の列挙型(enum, System.Enum, where System.Enum)は、環境や使い方によって速度が低下してしまいます。
最適化
CEnumは、下記の種類に応じて、parseとcastを最適化します。
- 数列タイプ
- 整列タイプ(値に欠番が無く、昇順に並んでいるもの、0オリジンである必要はない)
- 乱雑タイプ(整列タイプでも、Flagsタイプでもないもの)
- Flagsタイプ(Flags属性の付いたもの)
安全性
CEnumは、値の捏造を許しません。
CEnumに値を入れる手段は幾つかありますが、不正な値は、入り口で取り除かれます。
var c6 = (CETest)999;//数列タイプの場合、この値に対応するメンバーが定義されていなければ例外発生
var c7 = (CEFlag)(1<<30);//Flagsタイプの場合、Flagsの総論理和以外のビットが立っていれば例外発生
CEnumを用いると、数列タイプであれば、メンバー定義された値であることが保証され、Flagsタイプであれば、Flagsの取り得る値であることが保証されます。
標準の列挙型は、いくらでも値を捏造できてしまうので、バグの温床となってしまいます。
var e6 = (ETest)999;
var e7 = (EFlag)(1<<30);
互換性
標準のSystem.Enumクラスで、良く使われるであろうものは、一通り揃えてあります。
タイプ共通部分では、以下を用意しています。
public readonly T Value;
public readonly string Name;
public readonly string? EnumMemberValue;
string? ToString();
string? GetEnumMemberValue();
implicit operator Enum(CEnum<T> c);
implicit operator CEnum<T>(Enum Enum);
implicit operator T(CEnum<T> c);
explicit operator CEnum<T>(T Value);
explicit operator CEnum<T>(string Name);
const int Length;
Type GetUnderlyingType();
string GetName(CEnum<T> Member);
CEnum<T> Parse(T Value);
CEnum<T> Parse(string Names);
CEnum<T> Parse(string Names, bool ignoreCase);
bool TryParse(T Value, out CEnum<T>? Result);
bool TryParse(string Names, out CEnum<T>? Result);
bool TryParse(string Names, bool ignoreCase, out CEnum<T>? Result);
bool IsDefined(T Value);
bool IsDefined(string Names);
bool IsDefined(string Names, bool ignoreCase);
ReadOnlyCollection<CEnum<T>> GetMembers;
ReadOnlyCollection<T> GetValues;
ReadOnlyCollection<string> GetNames;
bool operator ==(CEnum<T> a, CEnum<T> b);
bool operator !=(CEnum<T> a, CEnum<T> b);
Parse(string...)、TryParse(string...)、IsDefined(string...)は、数列タイプとFlagsタイプで最適化が異なります。
- 数列タイプでは、キーワードを一つに限定し、高速化します
- Flagsタイプでは、System.Enumクラスと同様、複数のキーワードに対応します
- Parse()に関しては、「var c = (string)"member";」を用いることで、キーワードを一つに限定し、高速化できます
数列タイプの整列タイプでは、更に以下が追加されます。
const T Min;
const T Max;
Flagsタイプでは、更に以下を追加されます。
const T FlagsMask;
CEnum<T> operator &(CEnum<T> a, CEnum<T> b);
CEnum<T> operator |(CEnum<T> a, CEnum<T> b);
CEnum<T> operator ^(CEnum<T> a, CEnum<T> b);
CEnum<T> operator ~(CEnum<T> c);
bool HasFlag(CEnum<T> Flags);
bool AllFlag(CEnum<T> Flags);
bool AnyFlag(CEnum<T> Flags);
bool NotFlag(CEnum<T> Flags);
bool OtherAllFlag(CEnum<T> Flags);
bool OtherAnyFlag(CEnum<T> Flags);
bool OtherNotFlag(CEnum<T> Flags);
まとめ
enumの高速化を謳うものは幾つか見受けられますが、parse関連の高速化が主目的で、列挙型変数そのものの高速化が見当たらなかったので、作ってみました。
もしかしたら検索の仕方が悪いだけで、より良いものがあるかも知れませんが、Source Generatorsの学習ということで、今回はこれにて終了と致します。
最後までご覧いただき、有難う御座いました。
余談
前回からの変更点
- CEnum側のメンバー定数(const int)を廃止しました
- Enum側に定数がある
- CEnum class内で名前重複の可能性がある
- メンバー変数名のプリフィクスを、EからCに変更しました
- 列挙型名はC、メンバー名はE、というのは脈略がない
- CEnumは、列挙型名とメンバー名にCが付くんじゃい!の方が覚えやすい
- 継承は諦めました
- コンパイル順序の問題を解決できませんでした
- 似たようなことはできるので問題ないかなと
[CEnum] enum EParent {a,b,Term,}
[CEnum] enum EChild {c=EParent.Term,d,}