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?


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

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.


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;

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"

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


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

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

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.


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

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.


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

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ą.


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

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;
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.