Odbicie - uzyskaj nazwę atrybutu i wartość właściwości

253

Mam klasę, nazwijmy ją Zarezerwuj z właściwością o nazwie Nazwa. Z tą właściwością mam powiązany atrybut.

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

W mojej głównej metodzie używam refleksji i chcę uzyskać parę klucz-wartość dla każdego atrybutu dla każdej właściwości. Dlatego w tym przykładzie spodziewam się zobaczyć „Autor” dla nazwy atrybutu i „AutorName” dla wartości atrybutu.

Pytanie: Jak uzyskać nazwę i wartość atrybutu dla moich właściwości za pomocą Reflection?

developerdoug
źródło
co się dzieje, gdy próbujesz uzyskać dostęp do nieruchomości na tym obiekcie poprzez odbicie, utkniesz gdzieś lub chcesz kod do odbicia
Kobe

Odpowiedzi:

307

Użyj, typeof(Book).GetProperties()aby uzyskać tablicę PropertyInfoinstancji. Następnie użyj GetCustomAttributes()każdego PropertyInfoz nich, aby sprawdzić, czy któryś z nich ma Authortyp atrybutu. Jeśli tak, możesz uzyskać nazwę właściwości z informacji o właściwości i wartości atrybutów z atrybutu.

Coś wzdłuż tych linii, aby przeskanować typ w poszukiwaniu właściwości, które mają określony typ atrybutu i zwrócić dane w słowniku (zauważ, że można to uczynić bardziej dynamicznym poprzez przekazywanie typów do procedury):

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
źródło
16
Miałem nadzieję, że nie będę musiał rzucić atrybutu.
developerdoug
prop.GetCustomAttributes (true) zwraca tylko obiekt []. Jeśli nie chcesz rzutować, możesz użyć refleksji na temat samych instancji atrybutu.
Adam Markowitz,
Co to jest AuthorAttribute tutaj? Czy jest to klasa wywodząca się z atrybutu? @Adam Markowitz
Sarath Avanavu
1
Tak. OP używa niestandardowego atrybutu o nazwie „Autor”. Zobacz tutaj przykład: msdn.microsoft.com/en-us/library/sw480ze8.aspx
Adam Markowitz
1
Koszt wydajności rzutowania atrybutu jest zupełnie nieistotny w porównaniu z każdą inną zaangażowaną operacją (z wyjątkiem kontroli zerowej i przypisań ciągów).
SilentSin
112

Aby uzyskać wszystkie atrybuty właściwości ze słownika, użyj tego:

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

pamiętaj, aby zmienić z falsena, truejeśli chcesz dołączyć również odziedziczone atrybuty.

Mo Valipour
źródło
3
Robi to skutecznie to samo, co rozwiązanie Adama, ale jest o wiele bardziej zwięzłe.
Daniel Moore,
31
Dołącz .OfType <AuthorAttribue> () do wyrażenia zamiast ToDictionary, jeśli potrzebujesz tylko atrybutów autora i chcesz pominąć przyszłą obsadę
Adrian Zanescu
2
Czy ten wyjątek nie zostanie zgłoszony, gdy w tej samej właściwości będą dwa atrybuty tego samego typu?
Konstantin
53

Jeśli chcesz tylko jedną konkretną wartość atrybutu Na przykład Display Attribute, możesz użyć następującego kodu:

var pInfo = typeof(Book).GetProperty("Name")
                             .GetCustomAttribute<DisplayAttribute>();
var name = pInfo.Name;
maxspan
źródło
30

Rozwiązałem podobne problemy, pisząc Pomocnika atrybutu właściwości rozszerzenia ogólnego:

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

Stosowanie:

var author = AttributeHelper.GetPropertyAttributeValue<Book, string, AuthorAttribute, string>(prop => prop.Name, attr => attr.Author);
// author = "AuthorName"
Mikael Engver
źródło
1
Jak mogę uzyskać atrybut opisu z const Fields?
Amir
1
Otrzymasz: Błąd 1775 Członek 'Namespace.FieldName' nie może być dostępny za pomocą odwołania do instancji; zamiast tego zakwalifikuj go nazwą typu. Jeśli musisz to zrobić, proponuję zmienić „const” na „readonly”.
Mikael Engver
1
Szczerze mówiąc, powinieneś mieć o wiele bardziej użyteczny głos. To bardzo miła i przydatna odpowiedź na wiele przypadków.
David Létourneau
1
Dzięki @ DavidLétourneau! Można tylko mieć nadzieję. Wygląda na to, że trochę w tym pomogłeś.
Mikael Engver
:) Czy uważasz, że można mieć wartość wszystkich atrybutów dla jednej klasy za pomocą ogólnej metody i przypisać wartość atrybutu do każdej właściwości?
David Létourneau
21

Możesz użyć GetCustomAttributesData()i GetCustomAttributes():

var attributeData = typeof(Book).GetProperty("Name").GetCustomAttributesData();
var attributes = typeof(Book).GetProperty("Name").GetCustomAttributes(false);
Rozbite szkło
źródło
4
co za różnica?
Prime By Design
1
@PrimeByDesign Ten pierwszy dowiaduje się, jak utworzyć instancję zastosowanych atrybutów. Ten ostatni faktycznie tworzy te atrybuty.
HappyNomad
12

Jeśli masz na myśli „dla atrybutów, które biorą jeden parametr, wymień nazwy atrybutów i wartość parametru”, wtedy jest to łatwiejsze w .NET 4.5 poprzez 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
źródło
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
źródło
2

Chociaż powyższe najbardziej uprzywilejowane odpowiedzi zdecydowanie działają, sugeruję zastosowanie nieco innego podejścia w niektórych przypadkach.

Jeśli klasa ma wiele właściwości z zawsze tym samym atrybutem i chcesz posortować te atrybuty w słowniku, oto jak to zrobić:

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

Nadal używa rzutowania, ale zapewnia, że ​​rzutowanie będzie zawsze działać, ponieważ otrzymasz tylko niestandardowe atrybuty typu „AutorName”. Jeśli masz wiele atrybutów powyżej, odpowiedzi otrzymają wyjątek rzutowania.

Mirko Brandt
źródło
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);
    }
}

Stosowanie:

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

lub:

 //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();
Zwycięzca
źródło
1

Oto kilka metod statycznych, których można użyć do uzyskania wartości MaxLength lub dowolnego innego atrybutu.

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

}

Korzystanie z metody statycznej ...

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

Lub używając opcjonalnej metody rozszerzenia dla instancji ...

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

Lub używając pełnej metody statycznej dla dowolnego innego atrybutu (na przykład StringLength) ...

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

Zainspirowany odpowiedzią Mikaela Engvera.

Carter Medlin
źródło
1

Nekromancja.
Dla tych, którzy nadal muszą utrzymywać .NET 2.0, lub tych, którzy chcą to zrobić bez 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);
}

Przykładowe użycie:

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

lub po prostu

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

W tym przykładzie użyłem DisplayName zamiast Author, ponieważ ma pole o nazwie „DisplayName”, które ma być wyświetlane z wartością.

petrosmm
źródło
0

aby uzyskać atrybut z wyliczenia, używam:

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

// Za pomocą

 var _message = Translate(code);
Mohamed.Abdo
źródło
0

Po prostu szukam odpowiedniego miejsca na umieszczenie tego fragmentu kodu.

powiedzmy, że masz następującą właściwość:

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

I chcesz uzyskać wartość ShortName. Możesz to zrobić:

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

Lub ogólnie:

internal static string GetPropertyAttributeShortName(string propertyName)
{
    return ((DisplayAttribute)(typeof(SensorsModel).GetProperty(propertyName).GetCustomAttribute(typeof(DisplayAttribute)))).ShortName;
}
Asaf
źródło