Réflexion - obtenir le nom et la valeur d'attribut sur la propriété

253

J'ai une classe, appelons-la Book avec une propriété appelée Name. Avec cette propriété, j'ai un attribut qui lui est associé.

public class Book
{
    [Author("AuthorName")]
    public string Name
    {
        get; private set; 
    }
}

Dans ma méthode principale, j'utilise la réflexion et je souhaite obtenir une paire clé-valeur de chaque attribut pour chaque propriété. Donc, dans cet exemple, je m'attends à voir "Author" pour le nom de l'attribut et "AuthorName" pour la valeur de l'attribut.

Question: Comment obtenir le nom et la valeur d'attribut sur mes propriétés à l'aide de Reflection?

developerdoug
la source
ce qui se passe lorsque vous essayez d'accéder à des propriétés sur cet objet par réflexion, êtes-vous coincé quelque part ou voulez-vous du code pour la réflexion
kobe

Réponses:

308

Utilisez typeof(Book).GetProperties()pour obtenir un tableau d' PropertyInfoinstances. Utilisez ensuite GetCustomAttributes()sur chacun PropertyInfod' eux pour voir si l'un d'eux a le Authortype d'attribut. S'ils le font, vous pouvez obtenir le nom de la propriété à partir des informations de propriété et les valeurs d'attribut à partir de l'attribut.

Quelque chose le long de ces lignes pour analyser un type pour les propriétés qui ont un type d'attribut spécifique et pour retourner des données dans un dictionnaire (notez que cela peut être rendu plus dynamique en passant des types dans la routine):

public static Dictionary<string, string> GetAuthors()
{
    Dictionary<string, string> _dict = new Dictionary<string, string>();

    PropertyInfo[] props = typeof(Book).GetProperties();
    foreach (PropertyInfo prop in props)
    {
        object[] attrs = prop.GetCustomAttributes(true);
        foreach (object attr in attrs)
        {
            AuthorAttribute authAttr = attr as AuthorAttribute;
            if (authAttr != null)
            {
                string propName = prop.Name;
                string auth = authAttr.Name;

                _dict.Add(propName, auth);
            }
        }
    }

    return _dict;
}
Adam Markowitz
la source
16
J'espérais ne pas avoir à lancer l'attribut.
developerdoug
prop.GetCustomAttributes (true) ne renvoie qu'un objet []. Si vous ne voulez pas transtyper, vous pouvez utiliser la réflexion sur les instances d'attribut elles-mêmes.
Adam Markowitz
Qu'est-ce que AuthorAttribute ici? Est-ce une classe dérivée de Attribute? @Adam Markowitz
Sarath Avanavu
1
Oui. L'OP utilise un attribut personnalisé nommé «Auteur». Voir ici pour un exemple: msdn.microsoft.com/en-us/library/sw480ze8.aspx
Adam Markowitz
1
Le coût de performance de la conversion de l'attribut est tout à fait insignifiant par rapport à toutes les autres opérations impliquées (sauf la vérification nulle et les affectations de chaînes).
SilentSin
112

Pour obtenir tous les attributs d'une propriété dans un dictionnaire, utilisez ceci:

typeof(Book)
  .GetProperty("Name")
  .GetCustomAttributes(false) 
  .ToDictionary(a => a.GetType().Name, a => a);

n'oubliez pas de passer de falseà truesi vous souhaitez également inclure les attributs hérités.

Mo Valipour
la source
3
Cela fait effectivement la même chose que la solution d'Adam, mais est beaucoup plus concis.
Daniel Moore
31
Ajoutez .OfType <AuthorAttribue> () à l'expression au lieu de ToDictionary si vous n'avez besoin que d'attributs Author et que vous souhaitez ignorer une distribution future
Adrian Zanescu
2
Cette exception ne sera-t-elle pas levée lorsqu'il y a deux attributs du même type sur la même propriété?
Konstantin
53

Si vous voulez juste une valeur d'attribut spécifique Par exemple, Attribut d'affichage, vous pouvez utiliser le code suivant:

var pInfo = typeof(Book).GetProperty("Name")
                             .GetCustomAttribute<DisplayAttribute>();
var name = pInfo.Name;
maxspan
la source
30

J'ai résolu des problèmes similaires en écrivant un assistant d'attribut de propriété d'extension générique:

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

public static class AttributeHelper
{
    public static TValue GetPropertyAttributeValue<T, TOut, TAttribute, TValue>(
        Expression<Func<T, TOut>> propertyExpression, 
        Func<TAttribute, TValue> valueSelector) 
        where TAttribute : Attribute
    {
        var expression = (MemberExpression) propertyExpression.Body;
        var propertyInfo = (PropertyInfo) expression.Member;
        var attr = propertyInfo.GetCustomAttributes(typeof(TAttribute), true).FirstOrDefault() as TAttribute;
        return attr != null ? valueSelector(attr) : default(TValue);
    }
}

Usage:

var author = AttributeHelper.GetPropertyAttributeValue<Book, string, AuthorAttribute, string>(prop => prop.Name, attr => attr.Author);
// author = "AuthorName"
Mikael Engver
la source
1
Comment puis-je obtenir l'attribut de description de const Fields?
Amir
1
Vous obtiendrez: Erreur 1775 Member 'Namespace.FieldName' n'est pas accessible avec une référence d'instance; le qualifier avec un nom de type à la place. Si vous avez besoin de le faire, je vous suggère de remplacer «const» par «readonly».
Mikael Engver
1
Honnêtement, vous devriez avoir un vote beaucoup plus utile que cela. C'est une réponse très agréable et utile à de nombreux cas.
David Létourneau
1
Merci @ DavidLétourneau! On ne peut qu'espérer. On dirait que vous y avez un peu aidé.
Mikael Engver
:) Pensez-vous qu'il est possible d'avoir la valeur de tous les attributs pour une classe en utilisant votre méthode générique et d'attribuer la valeur de l'attribut à chaque propriété?
David Létourneau
21

Vous pouvez utiliser GetCustomAttributesData()et GetCustomAttributes():

var attributeData = typeof(Book).GetProperty("Name").GetCustomAttributesData();
var attributes = typeof(Book).GetProperty("Name").GetCustomAttributes(false);
Verre brisé
la source
4
quelle est la différence?
Prime By Design
1
@PrimeByDesign Le premier découvre comment instancier les attributs appliqués. Ce dernier instancie en fait ces attributs.
HappyNomad
12

Si vous voulez dire "pour les attributs qui prennent un paramètre, répertoriez les noms d'attribut et la valeur de paramètre", cela est plus facile dans .NET 4.5 via l' CustomAttributeDataAPI:

using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;

public static class Program
{
    static void Main()
    {
        PropertyInfo prop = typeof(Foo).GetProperty("Bar");
        var vals = GetPropertyAttributes(prop);
        // has: DisplayName = "abc", Browsable = false
    }
    public static Dictionary<string, object> GetPropertyAttributes(PropertyInfo property)
    {
        Dictionary<string, object> attribs = new Dictionary<string, object>();
        // look for attributes that takes one constructor argument
        foreach (CustomAttributeData attribData in property.GetCustomAttributesData()) 
        {

            if(attribData.ConstructorArguments.Count == 1)
            {
                string typeName = attribData.Constructor.DeclaringType.Name;
                if (typeName.EndsWith("Attribute")) typeName = typeName.Substring(0, typeName.Length - 9);
                attribs[typeName] = attribData.ConstructorArguments[0].Value;
            }

        }
        return attribs;
    }
}

class Foo
{
    [DisplayName("abc")]
    [Browsable(false)]
    public string Bar { get; set; }
}
Marc Gravell
la source
3
private static Dictionary<string, string> GetAuthors()
{
    return typeof(Book).GetProperties()
        .SelectMany(prop => prop.GetCustomAttributes())
        .OfType<AuthorAttribute>()
        .ToDictionary(attribute => attribute.Name, attribute => attribute.Name);
}
Daniel Dušek
la source
2

Bien que les réponses les plus votées ci-dessus fonctionnent certainement, je suggère d'utiliser une approche légèrement différente dans certains cas.

Si votre classe a plusieurs propriétés avec toujours le même attribut et que vous souhaitez obtenir ces attributs triés dans un dictionnaire, voici comment:

var dict = typeof(Book).GetProperties().ToDictionary(p => p.Name, p => p.GetCustomAttributes(typeof(AuthorName), false).Select(a => (AuthorName)a).FirstOrDefault());

Cela utilise toujours le cast mais garantit que le cast fonctionnera toujours car vous n'obtiendrez que les attributs personnalisés du type "AuthorName". Si vous aviez plusieurs attributs ci-dessus, les réponses obtiendraient une exception de conversion.

Mirko Brandt
la source
1
public static class PropertyInfoExtensions
{
    public static TValue GetAttributValue<TAttribute, TValue>(this PropertyInfo prop, Func<TAttribute, TValue> value) where TAttribute : Attribute
    {
        var att = prop.GetCustomAttributes(
            typeof(TAttribute), true
            ).FirstOrDefault() as TAttribute;
        if (att != null)
        {
            return value(att);
        }
        return default(TValue);
    }
}

Usage:

 //get class properties with attribute [AuthorAttribute]
        var props = typeof(Book).GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(AuthorAttribute)));
            foreach (var prop in props)
            {
               string value = prop.GetAttributValue((AuthorAttribute a) => a.Name);
            }

ou:

 //get class properties with attribute [AuthorAttribute]
        var props = typeof(Book).GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(AuthorAttribute)));
        IList<string> values = props.Select(prop => prop.GetAttributValue((AuthorAttribute a) => a.Name)).Where(attr => attr != null).ToList();
Victor
la source
1

Voici quelques méthodes statiques que vous pouvez utiliser pour obtenir la longueur maximale ou tout autre attribut.

using System;
using System.Linq;
using System.Reflection;
using System.ComponentModel.DataAnnotations;
using System.Linq.Expressions;

public static class AttributeHelpers {

public static Int32 GetMaxLength<T>(Expression<Func<T,string>> propertyExpression) {
    return GetPropertyAttributeValue<T,string,MaxLengthAttribute,Int32>(propertyExpression,attr => attr.Length);
}

//Optional Extension method
public static Int32 GetMaxLength<T>(this T instance,Expression<Func<T,string>> propertyExpression) {
    return GetMaxLength<T>(propertyExpression);
}


//Required generic method to get any property attribute from any class
public static TValue GetPropertyAttributeValue<T, TOut, TAttribute, TValue>(Expression<Func<T,TOut>> propertyExpression,Func<TAttribute,TValue> valueSelector) where TAttribute : Attribute {
    var expression = (MemberExpression)propertyExpression.Body;
    var propertyInfo = (PropertyInfo)expression.Member;
    var attr = propertyInfo.GetCustomAttributes(typeof(TAttribute),true).FirstOrDefault() as TAttribute;

    if (attr==null) {
        throw new MissingMemberException(typeof(T).Name+"."+propertyInfo.Name,typeof(TAttribute).Name);
    }

    return valueSelector(attr);
}

}

Utilisation de la méthode statique ...

var length = AttributeHelpers.GetMaxLength<Player>(x => x.PlayerName);

Ou en utilisant la méthode d'extension facultative sur une instance ...

var player = new Player();
var length = player.GetMaxLength(x => x.PlayerName);

Ou en utilisant la méthode statique complète pour tout autre attribut (StringLength par exemple) ...

var length = AttributeHelpers.GetPropertyAttributeValue<Player,string,StringLengthAttribute,Int32>(prop => prop.PlayerName,attr => attr.MaximumLength);

Inspiré par la réponse de Mikael Engver.

Carter Medlin
la source
1

Nécromancement.
Pour ceux qui doivent encore maintenir .NET 2.0, ou ceux qui veulent le faire sans LINQ:

public static object GetAttribute(System.Reflection.MemberInfo mi, System.Type t)
{
    object[] objs = mi.GetCustomAttributes(t, true);

    if (objs == null || objs.Length < 1)
        return null;

    return objs[0];
}



public static T GetAttribute<T>(System.Reflection.MemberInfo mi)
{
    return (T)GetAttribute(mi, typeof(T));
}


public delegate TResult GetValue_t<in T, out TResult>(T arg1);

public static TValue GetAttributValue<TAttribute, TValue>(System.Reflection.MemberInfo mi, GetValue_t<TAttribute, TValue> value) where TAttribute : System.Attribute
{
    TAttribute[] objAtts = (TAttribute[])mi.GetCustomAttributes(typeof(TAttribute), true);
    TAttribute att = (objAtts == null || objAtts.Length < 1) ? default(TAttribute) : objAtts[0];
    // TAttribute att = (TAttribute)GetAttribute(mi, typeof(TAttribute));

    if (att != null)
    {
        return value(att);
    }
    return default(TValue);
}

Exemple d'utilisation:

System.Reflection.FieldInfo fi = t.GetField("PrintBackground");
wkHtmlOptionNameAttribute att = GetAttribute<wkHtmlOptionNameAttribute>(fi);
string name = GetAttributValue<wkHtmlOptionNameAttribute, string>(fi, delegate(wkHtmlOptionNameAttribute a){ return a.Name;});

ou simplement

string aname = GetAttributValue<wkHtmlOptionNameAttribute, string>(fi, a => a.Name );
Stefan Steiger
la source
0
foreach (var p in model.GetType().GetProperties())
{
   var valueOfDisplay = 
       p.GetCustomAttributesData()
        .Any(a => a.AttributeType.Name == "DisplayNameAttribute") ? 
            p.GetCustomAttribute<DisplayNameAttribute>().DisplayName : 
            p.Name;
}

Dans cet exemple, j'ai utilisé DisplayName au lieu d'Auteur car il a un champ nommé «DisplayName» à afficher avec une valeur.

petrosmm
la source
0

pour obtenir l'attribut de enum, j'utilise:

 public enum ExceptionCodes
 {
  [ExceptionCode(1000)]
  InternalError,
 }

 public static (int code, string message) Translate(ExceptionCodes code)
        {
            return code.GetType()
            .GetField(Enum.GetName(typeof(ExceptionCodes), code))
            .GetCustomAttributes(false).Where((attr) =>
            {
                return (attr is ExceptionCodeAttribute);
            }).Select(customAttr =>
            {
                var attr = (customAttr as ExceptionCodeAttribute);
                return (attr.Code, attr.FriendlyMessage);
            }).FirstOrDefault();
        }

// En utilisant

 var _message = Translate(code);
Mohamed.Abdo
la source
0

Je cherche juste le bon endroit pour mettre ce morceau de code.

disons que vous avez la propriété suivante:

[Display(Name = "Solar Radiation (Average)", ShortName = "SolarRadiationAvg")]
public int SolarRadiationAvgSensorId { get; set; }

Et vous souhaitez obtenir la valeur ShortName. Tu peux faire:

((DisplayAttribute)(typeof(SensorsModel).GetProperty(SolarRadiationAvgSensorId).GetCustomAttribute(typeof(DisplayAttribute)))).ShortName;

Ou pour le rendre général:

internal static string GetPropertyAttributeShortName(string propertyName)
{
    return ((DisplayAttribute)(typeof(SensorsModel).GetProperty(propertyName).GetCustomAttribute(typeof(DisplayAttribute)))).ShortName;
}
Asaf
la source