2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-07-28

前振り

前回、高速だけれど、あまりにも面倒くさ過ぎる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,}
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?