Reflexão - obtém o nome e o valor do atributo na propriedade


253

Eu tenho uma classe, vamos chamá-lo de livro com uma propriedade chamada nome. Com essa propriedade, tenho um atributo associado a ela.

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

No meu método principal, estou usando a reflexão e desejo obter o par de valores-chave de cada atributo para cada propriedade. Portanto, neste exemplo, eu esperaria ver "Autor" para o nome do atributo e "AuthorName" para o valor do atributo.

Pergunta: Como obtenho o nome e o valor do atributo em minhas propriedades usando o Reflection?


O que está acontecendo quando você está tentando acessar a propriedade de no objeto por meio de reflexão, você está em algum lugar preso ou você quer código de reflexão
kobe

Respostas:


308

Use typeof(Book).GetProperties()para obter uma matriz de PropertyInfoinstâncias. Em seguida, use GetCustomAttributes()em cada um deles PropertyInfopara ver se algum deles tem o Authortipo de atributo. Se o fizerem, você pode obter o nome da propriedade nas informações da propriedade e os valores do atributo.

Algo ao longo dessas linhas para varrer um tipo em busca de propriedades que tenham um tipo de atributo específico e retornar dados em um dicionário (observe que isso pode ser mais dinâmico, passando tipos para a rotina):

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
Eu esperava que não tivesse que converter o atributo.
developerdoug

prop.GetCustomAttributes (true) retorna apenas um objeto []. Se você não deseja transmitir, pode usar a reflexão nas próprias instâncias de atributo.
Adam Markowitz

O que é o AuthorAttribute aqui? É uma classe derivada do Attribute?
@Adam

1
Sim. O OP está usando um atributo personalizado chamado 'Autor'. Veja aqui um exemplo: msdn.microsoft.com/en-us/library/sw480ze8.aspx
Adam Markowitz

1
O custo de desempenho da conversão do atributo é totalmente insignificante em comparação com todas as outras operações envolvidas (exceto a verificação nula e as atribuições de sequência).
SilentSin

112

Para obter todos os atributos de uma propriedade em um dicionário, use este:

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

lembre-se de mudar de falsepara truese você deseja incluir também atributos herdados.


3
Isso efetivamente faz o mesmo que a solução de Adam, mas é muito mais conciso.
Daniel Moore

31
Anexar .OfType <AuthorAttribue> () para a expressão em vez de ToDictionary se você só precisa de atributos autor e quiser pular um futuro elenco
Adrian Zanescu

2
Isso não lançará exceção quando houver dois atributos do mesmo tipo na mesma propriedade?
Konstantin

53

Se você deseja apenas um valor de Atributo específico. Por exemplo, Exibir Atributo, você pode usar o seguinte código:

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

30

Resolvi problemas semelhantes escrevendo um Auxiliar de atributo de propriedade de extensão 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"

1
Como posso obter o atributo description do const Fields?
Amir

1
Você receberá: Erro 1775 O 'Namespace.FieldName' não pode ser acessado com uma referência de instância; qualifique-o com um nome de tipo. Se você precisar fazer isso, sugiro alterar 'const' para 'readonly'.
Mikael Engver #

1
Você deveria ter um voto muito mais útil do que isso, honestamente. É uma resposta muito agradável e útil para muitos casos.
David Létourneau 14/01

1
Obrigado @ DavidLétourneau! Só se pode esperar. Parece que você ajudou um pouco nisso.
Mikael Engver

:) Você acha que é possível ter o valor de todos os atributos para uma classe usando seu método genérico e atribuir o valor do atributo a cada propriedade?
David Létourneau 15/01


12

Se você quer dizer "para atributos que usam um parâmetro, liste os nomes dos atributos e o valor do parâmetro", isso é mais fácil no .NET 4.5 por meio da 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

Embora as respostas mais votadas acima funcionem definitivamente, eu sugiro usar uma abordagem ligeiramente diferente em alguns casos.

Se sua classe tem várias propriedades sempre com o mesmo atributo e você deseja classificá-los em um dicionário, veja como:

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

Isso ainda usa conversão, mas garante que a conversão sempre funcione, pois você obterá apenas os atributos personalizados do tipo "AuthorName". Se você tivesse vários atributos acima, as respostas receberiam uma exceção de elenco.


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

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

1

Aqui estão alguns métodos estáticos que você pode usar para obter o MaxLength ou qualquer outro 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 o método estático ...

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

Ou usando o método de extensão opcional em uma instância ...

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

Ou usando o método estático completo para qualquer outro atributo (StringLength por exemplo) ...

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

Inspirado pela resposta de Mikael Engver.


1

Necromante.
Para aqueles que ainda precisam manter o .NET 2.0, ou aqueles que desejam fazê-lo sem o 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);
}

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

ou simplesmente

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

Neste exemplo, usei DisplayName em vez de Author porque ele possui um campo chamado 'DisplayName' a ser mostrado com um valor.


0

para obter atributo de enum, estou 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();
        }

// Usando

 var _message = Translate(code);

0

Apenas procurando o lugar certo para colocar esse código.

digamos que você tenha a seguinte propriedade:

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

E você deseja obter o valor ShortName. Você pode fazer:

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

Ou para torná-lo geral:

internal static string GetPropertyAttributeShortName(string propertyName)
{
    return ((DisplayAttribute)(typeof(SensorsModel).GetProperty(propertyName).GetCustomAttribute(typeof(DisplayAttribute)))).ShortName;
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.