Riflessione: ottieni il nome e il valore dell'attributo sulla proprietà


253

Ho un corso, chiamiamolo Libro con una proprietà chiamata Nome. Con quella proprietà, ho un attributo ad esso associato.

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

Nel mio metodo principale, sto usando la riflessione e desidero ottenere la coppia chiave-valore di ciascun attributo per ciascuna proprietà. Quindi, in questo esempio, mi aspetto di vedere "Autore" per il nome dell'attributo e "Nome autore" per il valore dell'attributo.

Domanda: Come posso ottenere il nome e il valore dell'attributo sulle mie proprietà usando Reflection?


cosa sta succedendo quando stai tentando di accedere alla proprietà di quell'oggetto attraverso la riflessione, sei bloccato da qualche parte o vuoi un codice per la riflessione
kobe,

Risposte:


308

Utilizzare typeof(Book).GetProperties()per ottenere una serie di PropertyInfoistanze. Quindi utilizzare GetCustomAttributes()su ciascuno PropertyInfoper vedere se uno di essi ha il Authortipo di attributo. In tal caso, è possibile ottenere il nome della proprietà dalle informazioni sulla proprietà e i valori dell'attributo dall'attributo.

Qualcosa lungo queste linee per scansionare un tipo alla ricerca di proprietà che hanno un tipo di attributo specifico e per restituire dati in un dizionario (si noti che questo può essere reso più dinamico passando i tipi nella 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;
}

16
Speravo di non dover lanciare l'attributo.
developerdoug

prop.GetCustomAttributes (true) restituisce solo un oggetto []. Se non si desidera eseguire il cast, è possibile utilizzare la riflessione sulle stesse istanze degli attributi.
Adam Markowitz,

Che cos'è AuthorAttribute qui? È una classe derivata da Attributo? @Adam Markowitz
Sarath Avanavu il

1
Sì. L'OP sta utilizzando un attributo personalizzato denominato "Autore". Vedi qui per un esempio: msdn.microsoft.com/en-us/library/sw480ze8.aspx
Adam Markowitz,

1
Il costo delle prestazioni del cast dell'attributo è assolutamente insignificante rispetto a qualsiasi altra operazione coinvolta (tranne il controllo null e le assegnazioni di stringhe).
SilentSin,

112

Per ottenere tutti gli attributi di una proprietà in un dizionario, utilizzare questo:

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

ricordati di cambiare da falsea truese vuoi includere anche gli attributi ereditati.


3
Questo fa effettivamente la stessa cosa della soluzione di Adam, ma è molto più conciso.
Daniel Moore,

31
Aggiungi .OfType <AuthorAttribue> () all'espressione invece di ToDictionary se hai solo bisogno degli attributi Author e vuoi saltare un cast futuro
Adrian Zanescu,

2
Questa eccezione non genererà due attributi dello stesso tipo sulla stessa proprietà?
Konstantin,

53

Se si desidera solo un valore di attributo specifico Ad esempio Visualizza attributo è possibile utilizzare il seguente codice:

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

30

Ho risolto problemi simili scrivendo un Helper di attributo di proprietà di estensione generico:

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

Uso:

var author = AttributeHelper.GetPropertyAttributeValue<Book, string, AuthorAttribute, string>(prop => prop.Name, attr => attr.Author);
// author = "AuthorName"

1
Come posso ottenere l'attributo di descrizione da const Fields?
Amir,

1
Verrà visualizzato il seguente messaggio: Errore 1775 Membro "Namespace.FieldName" non accessibile con un riferimento all'istanza; qualificalo invece con un nome di tipo. Se è necessario, suggerisco di cambiare "const" in "sola lettura".
Mikael Engver,

1
Dovresti avere un voto molto più utile di quello, onestamente. È una risposta molto utile e utile a molti casi.
David Létourneau,

1
Grazie @ DavidLétourneau! Si può solo sperare. Sembra che tu abbia aiutato un po 'in questo.
Mikael Engver,

:) Pensi che sia possibile avere il valore di tutti gli attributi per una classe usando il tuo metodo generico e assegnare il valore dell'attributo a ciascuna proprietà?
David Létourneau,


12

Se intendi "per gli attributi che accettano un parametro, elenca i nomi degli attributi e il valore del parametro", questo è più facile in .NET 4.5 tramite 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; }
}

3
private static Dictionary<string, string> GetAuthors()
{
    return typeof(Book).GetProperties()
        .SelectMany(prop => prop.GetCustomAttributes())
        .OfType<AuthorAttribute>()
        .ToDictionary(attribute => attribute.Name, attribute => attribute.Name);
}

2

Mentre le risposte più votate sopra funzionano sicuramente, suggerirei di utilizzare un approccio leggermente diverso in alcuni casi.

Se la tua classe ha più proprietà con sempre lo stesso attributo e desideri ottenere quegli attributi ordinati in un dizionario, ecco come:

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

Questo utilizza ancora il cast, ma garantisce che il cast funzionerà sempre poiché otterrai solo gli attributi personalizzati del tipo "NomeAutore". Se avessi più attributi sopra le risposte otterresti un'eccezione di cast.


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

Uso:

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

o:

 //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();

1

Ecco alcuni metodi statici che è possibile utilizzare per ottenere MaxLength o qualsiasi altro attributo.

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

}

Utilizzo del metodo statico ...

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

O usando il metodo di estensione opzionale su un'istanza ...

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

O usando il metodo statico completo per qualsiasi altro attributo (StringLength per esempio) ...

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

Ispirato dalla risposta di Mikael Engver.


1

Necromancing.
Per quelli che devono ancora mantenere .NET 2.0 o quelli che vogliono farlo senza 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);
}

Esempio di utilizzo:

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

o semplicemente

string aname = GetAttributValue<wkHtmlOptionNameAttribute, string>(fi, a => a.Name );

0
foreach (var p in model.GetType().GetProperties())
{
   var valueOfDisplay = 
       p.GetCustomAttributesData()
        .Any(a => a.AttributeType.Name == "DisplayNameAttribute") ? 
            p.GetCustomAttribute<DisplayNameAttribute>().DisplayName : 
            p.Name;
}

In questo esempio ho usato DisplayName invece di Author perché ha un campo chiamato 'DisplayName' da mostrare con un valore.


0

per ottenere l'attributo da enum, sto usando:

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

// Utilizzo

 var _message = Translate(code);

0

Sto solo cercando il posto giusto per inserire questo codice.

diciamo che hai la seguente proprietà:

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

E vuoi ottenere il valore ShortName. Tu puoi fare:

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

O per renderlo generale:

internal static string GetPropertyAttributeShortName(string propertyName)
{
    return ((DisplayAttribute)(typeof(SensorsModel).GetProperty(propertyName).GetCustomAttribute(typeof(DisplayAttribute)))).ShortName;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.