Comment lire un attribut sur une classe au moment de l'exécution?

107

J'essaie de créer une méthode générique qui lira un attribut sur une classe et retournera cette valeur au moment de l'exécution. Comment ferais-je cela?

Remarque: l'attribut DomainName est de classe DomainNameAttribute.

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

Ce que j'essaye de générer:

//This should return "MyTable"
String DomainNameValue = GetDomainName<MyClass>();
Zaffiro
la source
1
Lien officiel Microsoft: msdn.microsoft.com/en-us/library/71s1zwct.aspx
Mahesh
2
Question corollaire importante: comment obtenir tous les types dans l'assemblage avec l'attribut personnalisé stackoverflow.com/questions/2656189/...
Chris Marisic

Réponses:

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

METTRE À JOUR:

Cette méthode pourrait être généralisée pour fonctionner avec n'importe quel attribut:

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);
    }
}

et utilisez comme ceci:

string name = typeof(MyClass)
    .GetAttributeValue((DomainNameAttribute dna) => dna.Name);
Darin Dimitrov
la source
6
Merci pour votre diligence en répondant à la question!
Zaffiro
1
Cette méthode d'extension pourrait être généralisée davantage en étendant MemberInfo, une classe de base de Type et tous - ou au moins la plupart - des membres d'un Type. Cela ouvrirait cela pour permettre la lecture des attributs des propriétés, des champs et même des événements.
M.Babcock
4
Trop compliqué. Il n'est pas nécessaire d'utiliser lambda pour sélectionner la valeur d'attribut. Si vous en avez assez pour écrire le lambda, vous en savez assez pour accéder simplement au champ.
Darrel Lee
Comment puis-je étendre cette approche pour obtenir const Filedune classe statique?
Amir
51

Il existe déjà une extension pour ce faire.

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;
    }
}

Alors:

var attr = typeof(MyClass).GetCustomAttribute<DomainNameAttribute>(false);
return attr != null ? attr.DomainName : "";
Darrel Lee
la source
1
Vrai. Mais seulement .NET 4.5 et plus récent. Je suis toujours en train de développer du code de bibliothèque où je ne peux pas utiliser cette méthode :(
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
la source
5
Et +1 pour ne pas utiliser "var", il est donc facile de comprendre comment cela fonctionne.
RenniePet
Il ne compile pas. Mais "System.Reflection.MemberInfo info = typeof (MyClass) .GetTypeInfo ();" faire
Marcel James
4

J'ai utilisé la réponse de Darin Dimitrov pour créer une extension générique pour obtenir des attributs de membre pour n'importe quel membre d'une classe (au lieu d'attributs pour une classe). Je le poste ici car d'autres peuvent le trouver utile:

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);
    }
}

Exemple d'utilisation:

//Read System.ComponentModel Description Attribute from method 'MyMethodName' in class 'MyClass'
var Attribute = typeof(MyClass).GetAttribute("MyMethodName", (DescriptionAttribute d) => d.Description);
Sevin7
la source
L'héritage ne fonctionne pas sur les propriétés dérivées - pour cela, vous devrez appeler une méthode statique distincte (System.Attribute.GetCustomAttributes) stackoverflow.com/a/7175762/184910
Murraybiscuit
3

Une version simplifiée de la première solution de Darin Dimitrov:

public string GetDomainName<T>()
{
    var dnAttribute = typeof(T).GetCustomAttribute<DomainNameAttribute>(true);
    if (dnAttribute != null)
    {
        return dnAttribute.Name;
    }
    return null;
}
jk7
la source
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

Probablement tout aussi facile à utiliser le corps de la fonction générique en ligne. Cela n'a aucun sens pour moi de rendre la fonction générique sur le type MyClass.

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

Au cas où quelqu'un aurait besoin d'un résultat Nullable et pour que cela fonctionne sur Enums, PropertyInfo et classes, voici comment je l'ai résolu. Il s'agit d'une modification de la solution mise à jour de 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;
    }
}

Exemples d'utilisation:

// 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
la source
0

Plutôt que d'écrire beaucoup de code, faites simplement ceci:

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

Lorsque vous avez des méthodes remplacées avec le même nom, utilisez l'assistant ci-dessous

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);
        }

Utilisation: var someController = new SomeController (Certains paramètres); var str = typeof (SomeController) .GetControllerMethodAttributeValue (x => someController.SomeMethod (It.IsAny ()), (RouteAttribute routeAttribute) => routeAttribute.Template);

Vamsi J
la source