C#のEnum値におけるカスタム属性の効率的なアクセス

C#でアプリケーションを開発していると、列挙体(enum)値に関連付けられたカスタム属性を取得する必要に直面することがあります。このタスクは、一見すると簡単に思えるかもしれませんが、リフレクションやILコード生成に不慣れな方には特にそうです。しかし、enum値を文字列(その名前)に変換するだけでは、パフォーマンスの問題が生じることがよくあります。このブログ投稿では、文字列変換の手間なしにこれらの属性を取得する効率的でクリーンな方法を探ります。

問題の理解

カスタム属性が付与されたenumの例を示します。

public enum MyEnum {
    [CustomInfo("これはカスタム属性です")]
    None = 0,

    [CustomInfo("これは別の属性です")]
    ValueA,

    [CustomInfo("これは追加のフラグがあります", AllowSomething = true)]
    ValueB,
}

このコードスニペットでは、MyEnumという名前の列挙体が定義されており、複数の値が含まれており、それぞれのenumメンバーに関連する追加情報を示すCustomInfo属性が関連付けられています。

課題

最初の課題は、enumのインスタンスからこれらのCustomInfo属性を取得する必要があるときに発生します。最初に思いつく方法は、以下のコードを使用することです。

public CustomInfoAttribute GetInfo(MyEnum enumInput) {
    Type typeOfEnum = enumInput.GetType();
    FieldInfo fi = typeOfEnum.GetField(enumInput.ToString());
    return fi.GetCustomAttributes(typeof(CustomInfoAttribute), false).
        FirstOrDefault() as CustomInfoAttribute;
}

この関数は属性を効果的に取得しますが、enumInput.ToString()によるパフォーマンスの非効率性が影響します。これは、enum値をその文字列表現に変換するためです。

より良いアプローチ:ILコード生成を使用する

幸いなことに、ILコード生成を使用することで、enumを文字列に変換せずにカスタム属性に効率的にアクセスする方法があります。動的メソッドとILGeneratorを使用することで、オーバーヘッドを減らし、パフォーマンスを向上させることができます。以下は、プロパティに迅速にアクセスするためのデリゲートを作成し、属性取得にも適用できる方法について概要を示します。

ILコード生成の実装手順

  1. デリゲートの作成: オブジェクトを入力として受け取り、オブジェクトを返すデリゲートを定義します。これにより、高速なプロパティゲッターを作成することができます。

    public delegate object FastPropertyGetHandler(object target);
    
  2. ILの生成: ILGeneratorを使用して、ターゲットオブジェクトをスタックにロードし、ゲッターメソッドを呼び出し、戻り値を適切に処理するILメソッドを生成します。

    private static void EmitBoxIfNeeded(ILGenerator ilGenerator, System.Type type) {
        if (type.IsValueType) {
            ilGenerator.Emit(OpCodes.Box, type);
        }
    }
    
  3. プロパティゲッターの取得: 次に、動的メソッドを生成し、高速プロパティゲッターを作成するために必要なILコードを構築するメソッドを作ります。

    public static FastPropertyGetHandler GetPropertyGetter(PropertyInfo propInfo) {
        DynamicMethod dynamicMethod = new DynamicMethod(
            string.Empty, 
            typeof(object), 
            new Type[] { typeof(object) },
            propInfo.DeclaringType.Module);
    
        ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
        ilGenerator.Emit(OpCodes.Ldarg_0);
        ilGenerator.EmitCall(OpCodes.Callvirt, propInfo.GetGetMethod(), null);
        EmitBoxIfNeeded(ilGenerator, propInfo.PropertyType);
        ilGenerator.Emit(OpCodes.Ret);
        FastPropertyGetHandler getter = (FastPropertyGetHandler)dynamicMethod.CreateDelegate(typeof(FastPropertyGetHandler));
        return getter;
    }
    

結論

上記のメソッドを実装することで、enum値からの属性取得に伴うオーバーヘッドを効果的に減らすことができます。このアプローチでは、不要な文字列変換を排除するだけでなく、効率を求めるアプリケーションにとって重要な高速なランタイムパフォーマンスを実現します。

これらのテクニックをC#プロジェクトに組み込むことで、enum属性をスムーズに操作し、よりクリーンで維持管理しやすいコードを実現できます。楽しいコーディングを!