Effizienter Zugriff auf benutzerdefinierte Attribute bei Enum-Werten in C#

Wenn Sie Anwendungen in C# entwickeln, kann es manchmal erforderlich sein, benutzerdefinierte Attribute, die mit Enum-Werten verbunden sind, abzurufen. Diese Aufgabe mag auf den ersten Blick unkompliziert erscheinen, insbesondere für diejenigen, die mit Reflection und IL-Code-Generierung nicht vertraut sind. Das bloße Konvertieren von Enum-Werten in Strings (ihre Namen) kann jedoch häufig zu Leistungsproblemen führen. In diesem Blogbeitrag werden wir einen sauberen und effizienten Weg erkunden, um diese Attribute abzurufen, ohne uns mit String-Konvertierungen herumschlagen zu müssen.

Verständnis des Problems

Beginnen wir mit einem kurzen Beispiel, in dem wir ein Enum definieren, das mit benutzerdefinierten Attributen versehen ist:

public enum MeinEnum {
    [CustomInfo("Dies ist ein benutzerdefiniertes Attribut")]
    Keine = 0,

    [CustomInfo("Dies ist ein weiteres Attribut")]
    WertA,

    [CustomInfo("Dies hat ein zusätzliches Flag", AllowSomething = true)]
    WertB,
}

In diesem Snippet haben wir ein Enum namens MeinEnum, das mehrere Werte enthält, wobei jeder Wert mit einem CustomInfo-Attribut versehen ist, das zusätzliche Informationen zu jedem Enum-Mitglied liefert.

Die Herausforderung

Die wichtigste Herausforderung besteht darin, diese CustomInfo-Attribute aus einer Instanz des Enums abzurufen. Die erste Methode, die in den Sinn kommt, ist die Verwendung des folgenden Codes:

public CustomInfoAttribute GetInfo(MeinEnum enumEingabe) {
    Type typDesEnums = enumEingabe.GetType();
    FieldInfo fi = typDesEnums.GetField(enumEingabe.ToString());
    return fi.GetCustomAttributes(typeof(CustomInfoAttribute), false).
        FirstOrDefault() as CustomInfoAttribute;
}

Obwohl diese Funktion das Attribut effektiv abruft, leidet sie unter Leistungsineffizienzen aufgrund der Verwendung von enumEingabe.ToString(), die den Enum-Wert in seine String-Darstellung umwandelt.

Ein besserer Ansatz: IL-Code-Generierung verwenden

Glücklicherweise gibt es eine effizientere Methode, um benutzerdefinierte Attribute abzurufen, ohne Enums in Strings zu konvertieren, indem wir IL-Code-Generierung nutzen. Durch die Verwendung dynamischer Methoden und des ILGenerator reduzieren wir den Overhead und verbessern die Leistung. Im Folgenden skizzieren wir, wie wir einen Delegaten erstellen, der einen schnellen Zugriff auf Eigenschaften ermöglicht, was auch für den Abruf von Attributen angepasst werden kann.

Schritte zur Implementierung der IL-Code-Generierung

  1. Delegat erstellen: Definieren Sie einen Delegaten, der ein Objekt akzeptiert und ein Objekt zurückgibt. Dies ermöglicht uns, einen schnellen Property-Getter zu erstellen.

    public delegate object SchnellerPropertyGetHandler(object ziel);
    
  2. IL erzeugen: Mit dem ILGenerator können wir eine IL-Methode erstellen, die das Zielobjekt auf den Stack lädt, die Getter-Methode aufruft und den Rückgabewert korrekt behandelt.

    private static void EmitBoxIfNeeded(ILGenerator ilGenerator, System.Type typ) {
        if (typ.IsValueType) {
            ilGenerator.Emit(OpCodes.Box, typ);
        }
    }
    
  3. Getter für Eigenschaften abrufen: Jetzt erstellen wir eine Methode, um eine dynamische Methode zu generieren und den notwendigen IL-Code zu erstellen, um einen schnellen Property-Getter zu erzeugen:

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

Fazit

Durch die Implementierung der oben skizzierten Methode können Sie den Overhead, der mit dem Abruf von Attributen aus Enum-Werten verbunden ist, effektiv reduzieren. Dieser Ansatz beseitigt nicht nur unnötige String-Konvertierungen, sondern sorgt auch für eine schnellere Laufzeitleistung – entscheidend für Anwendungen, die Effizienz erfordern.

Die Integration dieser Techniken in Ihre C#-Projekte wird Ihnen helfen, Enum-Attribute reibungslos zu navigieren und saubereren, wartungsfreundlicheren Code zu erstellen. Viel Spaß beim Programmieren!