Reflexión: obtenga el nombre y el valor del atributo en la propiedad

253

Tengo una clase, llamémosla Libro con una propiedad llamada Nombre. Con esa propiedad, tengo un atributo asociado.

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

En mi método principal, estoy usando la reflexión y deseo obtener un par de valores clave de cada atributo para cada propiedad. Entonces, en este ejemplo, esperaría ver "Autor" para el nombre del atributo y "AuthorName" para el valor del atributo.

Pregunta: ¿Cómo obtengo el nombre y el valor del atributo en mis propiedades usando Reflection?

desarrollador
fuente
qué está sucediendo cuando usted está tratando de acceder a la propiedad de ese objeto a través de la reflexión, es que en algún lugar atascado o desea código de reflexión
Kobe

Respuestas:

308

Se usa typeof(Book).GetProperties()para obtener una variedad de PropertyInfoinstancias. Luego use GetCustomAttributes()en cada uno PropertyInfopara ver si alguno de ellos tiene el Authortipo de Atributo. Si lo hacen, puede obtener el nombre de la propiedad de la información de la propiedad y los valores de los atributos del atributo.

Algo a lo largo de estas líneas para escanear un tipo de propiedades que tienen un tipo de atributo específico y para devolver datos en un diccionario (tenga en cuenta que esto se puede hacer más dinámico al pasar los tipos a la rutina):

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
fuente
16
Esperaba no tener que lanzar el atributo.
developerdoug
prop.GetCustomAttributes (true) solo devuelve un objeto []. Si no desea emitir, puede utilizar la reflexión sobre las propias instancias de atributos.
Adam Markowitz
¿Qué es AuthorAttribute aquí? ¿Es una clase que se deriva de Attribute? @Adam Markowitz
Sarath Avanavu
1
Si. El OP está utilizando un atributo personalizado llamado 'Autor'. Vea aquí un ejemplo: msdn.microsoft.com/en-us/library/sw480ze8.aspx
Adam Markowitz
1
El costo de rendimiento de convertir el atributo es completamente insignificante en comparación con todas las demás operaciones involucradas (excepto la verificación nula y las asignaciones de cadenas).
SilentSin
112

Para obtener todos los atributos de una propiedad en un diccionario, use esto:

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

recuerde cambiar de falsea truesi desea incluir también atributos heredados.

Mo Valipour
fuente
3
Esto hace efectivamente lo mismo que la solución de Adam, pero es mucho más conciso.
Daniel Moore
31
Agregue .OfType <AuthorAttribue> () a la expresión en lugar de ToDictionary si solo necesita atributos de Autor y desea omitir un elenco futuro
Adrian Zanescu
2
¿No arrojará esta excepción cuando hay dos atributos del mismo tipo en la misma propiedad?
Konstantin
53

Si solo desea un valor de Atributo específico. Por ejemplo, Mostrar Atributo, puede usar el siguiente código:

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

He resuelto problemas similares escribiendo un Asistente de atributos de propiedad de extensión genérica:

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"
Mikael Engver
fuente
1
¿Cómo puedo obtener el atributo de descripción de const Fields?
Amir
1
Obtendrá un: Error 1775 No se puede acceder al miembro 'espacio de nombres.Nombre del campo' con una referencia de instancia; califíquelo con un nombre de tipo en su lugar. Si necesita hacer esto, sugiero cambiar 'const' a 'readonly'.
Mikael Engver
1
Debería tener un voto mucho más útil que eso, sinceramente. Es una respuesta muy agradable y útil para muchos casos.
David Létourneau
1
Gracias @ DavidLétourneau! Uno solo puede esperar. Parece que ayudaste un poco en eso.
Mikael Engver
:) ¿Cree que es posible tener el valor de todos los atributos para una clase utilizando su método genérico y asignar el valor del atributo a cada propiedad?
David Létourneau
21

Puedes usar GetCustomAttributesData()y GetCustomAttributes():

var attributeData = typeof(Book).GetProperty("Name").GetCustomAttributesData();
var attributes = typeof(Book).GetProperty("Name").GetCustomAttributes(false);
Vidrio roto
fuente
44
¿cual es la diferencia?
Prime By Design
1
@PrimeByDesign El primero descubre cómo instanciar los atributos aplicados. El último en realidad instancia esos atributos.
HappyNomad
12

Si quiere decir "para los atributos que toman un parámetro, enumere los nombres de los atributos y el valor del parámetro", esto es más fácil en .NET 4.5 a través de la 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
fuente
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
fuente
2

Si bien las respuestas más votadas anteriormente definitivamente funcionan, sugeriría usar un enfoque ligeramente diferente en algunos casos.

Si su clase tiene varias propiedades con siempre el mismo atributo y desea ordenar esos atributos en un diccionario, así es como:

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

Esto todavía usa la conversión, pero asegura que la conversión siempre funcionará, ya que solo obtendrá los atributos personalizados del tipo "AuthorName". Si tuviera múltiples Atributos las respuestas anteriores obtendrían una excepción de lanzamiento.

Mirko Brandt
fuente
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();
Víctor
fuente
1

Aquí hay algunos métodos estáticos que puede usar para obtener MaxLength o cualquier otro atributo.

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

}

Usando el método estático ...

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

O usando el método de extensión opcional en una instancia ...

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

O usando el método estático completo para cualquier otro atributo (StringLength por ejemplo) ...

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

Inspirado por la respuesta de Mikael Engver.

Carter Medlin
fuente
1

Nigromancia
Para aquellos que todavía tienen que mantener .NET 2.0, o aquellos que quieren hacerlo sin 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);
}

Ejemplo de uso:

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 simplemente

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

En este ejemplo, usé DisplayName en lugar de Author porque tiene un campo llamado 'DisplayName' que se muestra con un valor.

petrosmm
fuente
0

para obtener un atributo de enum, estoy 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();
        }

// Utilizando

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

Solo estoy buscando el lugar correcto para poner este código.

Digamos que tiene la siguiente propiedad:

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

Y desea obtener el valor ShortName. Tu puedes hacer:

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

O para hacerlo general:

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