Wie lese ich zur Laufzeit ein Attribut für eine Klasse?

106

Ich versuche, eine generische Methode zu erstellen, die ein Attribut für eine Klasse liest und diesen Wert zur Laufzeit zurückgibt. Wie würde ich das machen?

Hinweis: Das DomainName-Attribut gehört zur Klasse DomainNameAttribute.

[DomainName("MyTable")]
Public class MyClass : DomainBase
{}

Was ich zu generieren versuche:

//This should return "MyTable"
String DomainNameValue = GetDomainName<MyClass>();
Zaffiro
quelle
1
Offizieller Microsoft Link: msdn.microsoft.com/en-us/library/71s1zwct.aspx
Mahesh
2
Wichtige Folgefrage, wie alle Typen mit dem benutzerdefinierten Attribut stackoverflow.com/questions/2656189/…
Chris Marisic

Antworten:

235
public string GetDomainName<T>()
{
    var dnAttribute = typeof(T).GetCustomAttributes(
        typeof(DomainNameAttribute), true
    ).FirstOrDefault() as DomainNameAttribute;
    if (dnAttribute != null)
    {
        return dnAttribute.Name;
    }
    return null;
}

AKTUALISIEREN:

Diese Methode könnte weiter verallgemeinert werden, um mit jedem Attribut zu arbeiten:

public static class AttributeExtensions
{
    public static TValue GetAttributeValue<TAttribute, TValue>(
        this Type type, 
        Func<TAttribute, TValue> valueSelector) 
        where TAttribute : Attribute
    {
        var att = type.GetCustomAttributes(
            typeof(TAttribute), true
        ).FirstOrDefault() as TAttribute;
        if (att != null)
        {
            return valueSelector(att);
        }
        return default(TValue);
    }
}

und verwenden Sie wie folgt:

string name = typeof(MyClass)
    .GetAttributeValue((DomainNameAttribute dna) => dna.Name);
Darin Dimitrov
quelle
6
Vielen Dank für Ihre Sorgfalt bei der Beantwortung der Frage!
Zaffiro
1
Diese Erweiterungsmethode könnte weiter verallgemeinert werden, indem MemberInfo, eine Basisklasse von Type, und alle - oder zumindest die meisten - Mitglieder eines Type erweitert werden. Dies würde dies öffnen, um das Lesen von Attributen aus Eigenschaften, Feldern und sogar Ereignissen zu ermöglichen.
M.Babcock
4
Übermäßig kompliziert. Es ist nicht erforderlich, Lambda zu verwenden, um den Attributwert auszuwählen. Wenn Sie genug haben, um das Lambda zu schreiben, wissen Sie genug, um nur auf das Feld zuzugreifen.
Darrel Lee
Wie kann ich diesen Ansatz erweitern, um const Filedin die statische Klasse zu gelangen ?
Amir
51

Dafür gibt es bereits eine Erweiterung.

namespace System.Reflection
{
    // Summary:
    //     Contains static methods for retrieving custom attributes.
    public static class CustomAttributeExtensions
    {
        public static T GetCustomAttribute<T>(this MemberInfo element, bool inherit) where T : Attribute;
    }
}

So:

var attr = typeof(MyClass).GetCustomAttribute<DomainNameAttribute>(false);
return attr != null ? attr.DomainName : "";
Darrel Lee
quelle
1
Wahr. Aber nur .NET 4.5 und neuer. Ich entwickle noch Bibliothekscode, wo ich diese Methode nicht verwenden kann :(
andreas
15
System.Reflection.MemberInfo info = typeof(MyClass);
object[] attributes = info.GetCustomAttributes(true);

for (int i = 0; i < attributes.Length; i++)
{
    if (attributes[i] is DomainNameAttribute)
    {
        System.Console.WriteLine(((DomainNameAttribute) attributes[i]).Name);
    }   
}
Merritt
quelle
5
Und +1, wenn Sie "var" nicht verwenden, damit Sie leicht verstehen, wie es funktioniert.
RenniePet
Es wird nicht kompiliert. Aber "System.Reflection.MemberInfo info = typeof (MyClass) .GetTypeInfo ();" tun
Marcel James
4

Ich habe Darin Dimitrovs Antwort verwendet, um eine generische Erweiterung zu erstellen, mit der Mitgliedsattribute für jedes Mitglied in einer Klasse abgerufen werden können (anstelle von Attributen für eine Klasse). Ich poste es hier, weil andere es vielleicht nützlich finden:

public static class AttributeExtensions
{
    /// <summary>
    /// Returns the value of a member attribute for any member in a class.
    ///     (a member is a Field, Property, Method, etc...)    
    /// <remarks>
    /// If there is more than one member of the same name in the class, it will return the first one (this applies to overloaded methods)
    /// </remarks>
    /// <example>
    /// Read System.ComponentModel Description Attribute from method 'MyMethodName' in class 'MyClass': 
    ///     var Attribute = typeof(MyClass).GetAttribute("MyMethodName", (DescriptionAttribute d) => d.Description);
    /// </example>
    /// <param name="type">The class that contains the member as a type</param>
    /// <param name="MemberName">Name of the member in the class</param>
    /// <param name="valueSelector">Attribute type and property to get (will return first instance if there are multiple attributes of the same type)</param>
    /// <param name="inherit">true to search this member's inheritance chain to find the attributes; otherwise, false. This parameter is ignored for properties and events</param>
    /// </summary>    
    public static TValue GetAttribute<TAttribute, TValue>(this Type type, string MemberName, Func<TAttribute, TValue> valueSelector, bool inherit = false) where TAttribute : Attribute
    {
        var att = type.GetMember(MemberName).FirstOrDefault().GetCustomAttributes(typeof(TAttribute), inherit).FirstOrDefault() as TAttribute;
        if (att != null)
        {
            return valueSelector(att);
        }
        return default(TValue);
    }
}

Anwendungsbeispiel:

//Read System.ComponentModel Description Attribute from method 'MyMethodName' in class 'MyClass'
var Attribute = typeof(MyClass).GetAttribute("MyMethodName", (DescriptionAttribute d) => d.Description);
Sevin7
quelle
Die Vererbung funktioniert nicht bei abgeleiteten Eigenschaften. Dazu müssen Sie eine separate statische Methode (System.Attribute.GetCustomAttributes) aufrufen. Stackoverflow.com/a/7175762/184910
murraybiscuit
3

Eine vereinfachte Version von Darin Dimitrovs erster Lösung:

public string GetDomainName<T>()
{
    var dnAttribute = typeof(T).GetCustomAttribute<DomainNameAttribute>(true);
    if (dnAttribute != null)
    {
        return dnAttribute.Name;
    }
    return null;
}
jk7
quelle
0
' Simplified Generic version. 
Shared Function GetAttribute(Of TAttribute)(info As MemberInfo) As TAttribute
    Return info.GetCustomAttributes(GetType(TAttribute), _
                                    False).FirstOrDefault()
End Function

' Example usage over PropertyInfo
Dim fieldAttr = GetAttribute(Of DataObjectFieldAttribute)(pInfo)
If fieldAttr IsNot Nothing AndAlso fieldAttr.PrimaryKey Then
    keys.Add(pInfo.Name)
End If

Wahrscheinlich genauso einfach, den Körper der generischen Funktion inline zu verwenden. Es macht für mich keinen Sinn, die Funktion über den Typ MyClass generisch zu machen.

string DomainName = GetAttribute<DomainNameAttribute>(typeof(MyClass)).Name
// null reference exception if MyClass doesn't have the attribute.
Darrel Lee
quelle
0

Für den Fall, dass jemand ein nullbares Ergebnis benötigt und dies für Enums, PropertyInfo und Klassen funktioniert, habe ich es wie folgt gelöst. Dies ist eine Modifikation der aktualisierten Lösung von Darin Dimitrov.

public static object GetAttributeValue<TAttribute, TValue>(this object val, Func<TAttribute, TValue> valueSelector) where TAttribute : Attribute
{
    try
    {
        Type t = val.GetType();
        TAttribute attr;
        if (t.IsEnum && t.GetField(val.ToString()).GetCustomAttributes(typeof(TAttribute), true).FirstOrDefault() is TAttribute att)
        {
            // Applies to Enum values
            attr = att;
        }
        else if (val is PropertyInfo pi && pi.GetCustomAttributes(typeof(TAttribute), true).FirstOrDefault() is TAttribute piAtt)
        {
            // Applies to Properties in a Class
            attr = piAtt;
        }
        else
        {
            // Applies to classes
            attr = (TAttribute)t.GetCustomAttributes(typeof(TAttribute), false).FirstOrDefault();
        }
        return valueSelector(attr);
    }
    catch
    {
        return null;
    }
}

Anwendungsbeispiele:

// Class
SettingsEnum.SettingGroup settingGroup = (SettingsEnum.SettingGroup)(this.GetAttributeValue((SettingGroupAttribute attr) => attr.Value) as SettingsEnum.SettingGroup?);

// Enum
DescriptionAttribute desc = settingGroup.GetAttributeValue((DescriptionAttribute attr) => attr) as DescriptionAttribute;

// PropertyInfo       
foreach (PropertyInfo pi in this.GetType().GetProperties())
{
    string setting = ((SettingsEnum.SettingName)(pi.GetAttributeValue((SettingNameAttribute attr) => attr.Value) as SettingsEnum.SettingName?)).ToString();
}
Mideus
quelle
0

Schreiben Sie lieber viel Code, als dies zu tun:

{         
   dynamic tableNameAttribute = typeof(T).CustomAttributes.FirstOrDefault().ToString();
   dynamic tableName = tableNameAttribute.Substring(tableNameAttribute.LastIndexOf('.'), tableNameAttribute.LastIndexOf('\\'));    
}
Naeem Ahmed
quelle
0

Wenn Sie überschriebene Methoden mit demselben Namen haben Verwenden Sie den folgenden Helfer

public static TValue GetControllerMethodAttributeValue<T, TT, TAttribute, TValue>(this T type, Expression<Func<T, TT>> exp, Func<TAttribute, TValue> valueSelector) where TAttribute : Attribute
        {
            var memberExpression = exp?.Body as MethodCallExpression;

            if (memberExpression.Method.GetCustomAttributes(typeof(TAttribute), false).FirstOrDefault() is TAttribute attr && valueSelector != null)
            {
                return valueSelector(attr);
            }

            return default(TValue);
        }

Verwendung: var someController = neuer SomeController (einige Parameter); var str = typeof (SomeController) .GetControllerMethodAttributeValue (x => someController.SomeMethod (It.IsAny ()), (RouteAttribute routeAttribute) => routeAttribute.Template);

Vamsi J.
quelle