（c#）面倒くさ過ぎるCEnumをSource Generatorsで自動生成してみた

# 前振り

[前回](https://qiita.com/EmuraDaisuke/items/5ac564249ef973c042fe)、高速だけれど、あまりにも面倒くさ過ぎるEnumイディオム、CEnumを紹介しました。  

今回は、その面倒くさい部分をSource Generatorsで自動生成しつつ、色々と便利にしてみました。  

ソースコードは、[ここ](https://github.com/EmuraDaisuke/CEnumGenerator)にあります。  

# 本題

## 使い方

usingを定義します。  
~~~ c#
using CEnum;
~~~
<br>

enumの前に[CEnum]属性を付けます。  
~~~ c#
[CEnum] enum ETest {a,b,}
~~~
<br>

Flags属性とEnumMember属性にも対応しています。  
~~~ c#
[CEnum, Flags] enum EFlag {[EnumMember(Value="A")]a=(1<<0),[EnumMember(Value="B")]b=(1<<1),}
~~~
<br>

enumからCEnumへの変換は、以下の通りです。  
~~~ c#
var c0 = (CETest)ETest.a;
CETest c1 = ETest.b;
~~~

CEnumからenumへの変換は、以下の通りです。  
~~~ c#
var e0 = (ETest)c0;
Etest e1 = c1;
~~~
<br>

enumから変換することなく、CEnumで完結させた方が、高速且つ、安全です。  
~~~ c#
var c2 = CETest.Ca; // enum名とメンバー名に、Cプリフィクスを付けます
var c3 = CETest.Cb; // enum名とメンバー名に、Cプリフィクスを付けます（大事なことなので、２度書きました）
~~~
<br>

switchは、このように書けますが、  
~~~ c#
switch (c2){
    case (int)ETest.a: break;
    case (int)ETest.b: break;
}
~~~

Valueを引いた方が高速です。  
~~~ c#
switch (c2.Value){
    case (int)ETest.a: break;
    case (int)ETest.b: break;
}
~~~
<br>

CEnumは、Lengthを簡単に引けるので便利です。  
~~~ c#
var n = CETest.Length;
~~~

例えば、配列の確保を、このように短く書けます。
~~~ c#
var a0 = new string[CETest.Length];
a0[(int)ETest.a] = "a";
a0[(int)ETest.b] = "b";
var s0 = a0[c2.Value];
~~~
<br>

Flags属性が付いていれば、論理演算が可能になります。  
~~~ c#
var c4 = CEFlag.Ca;
var c5 = c4 | CEFlag.Cb;
~~~

HasFlag()以外にも、便利なメソッドが追加されます。  
~~~ c#
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)
~~~
<br>

EnumMemberのValueを引くことも出来ます。  
~~~ c#
var m4 = c4.GetEnumMemberValue();
~~~
<br>

そして最後に、classの中で定義したenumに[CEnum]を付ける場合、そのclassをpartialにする必要がありますので、ご注意ください。  
~~~ c#
partial class Test
{
    [CEnum] enum ETest {a,b,}
}
~~~


## 特徴

「列挙型変数の高速化」が、CEnumの主目的です。  

標準の列挙型(enum, System.Enum, where System.Enum)は、環境や使い方によって速度が[低下](https://qiita.com/EmuraDaisuke/items/5ac564249ef973c042fe#%E3%83%99%E3%83%B3%E3%83%81%E3%83%9E%E3%83%BC%E3%82%AF)してしまいます。  
<br>

### 最適化

CEnumは、下記の種類に応じて、parseとcastを最適化します。  
- 数列タイプ
  - 整列タイプ（値に欠番が無く、昇順に並んでいるもの、0オリジンである必要はない）
  - 乱雑タイプ（整列タイプでも、Flagsタイプでもないもの）
- Flagsタイプ（Flags属性の付いたもの）
<br>

### 安全性

CEnumは、値の捏造を許しません。  

CEnumに値を入れる手段は幾つかありますが、不正な値は、入り口で取り除かれます。  
~~~ c#
var c6 = (CETest)999;//数列タイプの場合、この値に対応するメンバーが定義されていなければ例外発生
var c7 = (CEFlag)(1<<30);//Flagsタイプの場合、Flagsの総論理和以外のビットが立っていれば例外発生
~~~
CEnumを用いると、数列タイプであれば、メンバー定義された値であることが保証され、Flagsタイプであれば、Flagsの取り得る値であることが保証されます。  
<br>

標準の列挙型は、いくらでも値を捏造できてしまうので、バグの温床となってしまいます。  
~~~ c#
var e6 = (ETest)999;
var e7 = (EFlag)(1<<30);
~~~
<br>

### 互換性

標準のSystem.Enumクラスで、良く使われるであろうものは、一通り揃えてあります。  

タイプ共通部分では、以下を用意しています。
~~~ c#
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";」を用いることで、キーワードを一つに限定し、高速化できます
<br>

数列タイプの整列タイプでは、更に以下が追加されます。  
~~~ c#
const T Min;
const T Max;
~~~
<br>

Flagsタイプでは、更に以下を追加されます。  
~~~ c#
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);
~~~
<br>

# まとめ

enumの高速化を謳うものは幾つか見受けられますが、parse関連の高速化が主目的で、列挙型変数そのものの高速化が見当たらなかったので、作ってみました。  

もしかしたら検索の仕方が悪いだけで、より良いものがあるかも知れませんが、Source Generatorsの学習ということで、今回はこれにて終了と致します。  

最後までご覧いただき、有難う御座いました。  
<br>

# 余談

前回からの変更点
- CEnum側のメンバー定数(const int)を廃止しました
  - Enum側に定数がある
  - CEnum class内で名前重複の可能性がある
- メンバー変数名のプリフィクスを、EからCに変更しました
  - 列挙型名はC、メンバー名はE、というのは脈略がない
  - CEnumは、列挙型名とメンバー名にCが付くんじゃい！の方が覚えやすい
- 継承は諦めました
  - コンパイル順序の問題を解決できませんでした
  - 似たようなことはできるので問題ないかなと
~~~ c#
[CEnum] enum EParent {a,b,Term,}
[CEnum] enum EChild {c=EParent.Term,d,}
~~~