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?

developerdoug
fonte
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;
}
Adam Markowitz
fonte
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.

Mo Valipour
fonte
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;
maxspan
fonte
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"
Mikael Engver
fonte
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
21

Você pode usar GetCustomAttributesData()e GetCustomAttributes():

var attributeData = typeof(Book).GetProperty("Name").GetCustomAttributesData();
var attributes = typeof(Book).GetProperty("Name").GetCustomAttributes(false);
Vidro quebrado
fonte
4
qual é a diferença?
O primeiro-By Design
1
@PrimeByDesign O primeiro descobre como instanciar os atributos aplicados. O último realmente instancia esses atributos.
HappyNomad
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; }
}
Marc Gravell
fonte
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
fonte
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.

Mirko Brandt
fonte
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();
Vencedor
fonte
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.

Carter Medlin
fonte
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 );
Stefan Steiger
fonte
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.

petrosmm
fonte
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);
Mohamed.Abdo
fonte
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;
}
Asaf
fonte