Reflection - отримайте ім'я атрибута та значення для властивості


253

У мене є клас, давайте назвати його Book із властивістю під назвою Name. З цим властивістю у мене пов'язаний атрибут.

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

У своєму головному методі я використовую роздуми і хочу отримати пару ключових значень кожного атрибута для кожного властивості. Тож у цьому прикладі я очікую побачити "Author" для імені атрибута та "AuthorName" для значення атрибута.

Питання: Як отримати ім’я та значення атрибута для моїх властивостей за допомогою Reflection?


що відбувається, коли ви намагаєтесь отримати доступ до об'єкта власності на цьому об'єкті через рефлексію, ви десь застрягли чи хочете код для роздуму
kobe

Відповіді:


307

Використовуйте typeof(Book).GetProperties()для отримання масиву PropertyInfoекземплярів. Потім використовуйте GetCustomAttributes()кожен, PropertyInfoщоб побачити, чи є який-небудь з них Authorтип атрибутів. Якщо вони є, ви можете отримати ім'я властивості з інформації про властивість та значення атрибута з атрибута.

Щось уздовж цих рядків, щоб сканувати тип для властивостей, які мають певний тип атрибутів, і повернути дані у словник (зауважте, що це може бути більш динамічним, передаючи типи в рутину):

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
Я сподівався, що мені не доведеться робити атрибут.
developerdoug

prop.GetCustomAttributes (true) повертає лише об'єкт []. Якщо ви не хочете робити трансляцію, ви можете використати роздуми про самі екземпляри атрибутів.
Адам Марковіц

Що тут AuthorAttribute? Це клас, який походить від Attribute? @Adam Markowitz
Сарат Аванаву

1
Так. В ОП використовується спеціальний атрибут під назвою "Автор". Дивіться тут приклад: msdn.microsoft.com/en-us/library/sw480ze8.aspx
Адам Марковіц

1
Вартість продуктивності перенесення атрибута є абсолютно незначною порівняно з будь-якою іншою операцією, що займається (за винятком нульової перевірки та призначення рядків).
SilentSin

112

Щоб отримати всі атрибути властивості у словнику, використовуйте це:

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

не забудьте змінити з falseна, trueякщо ви хочете включити також успадковані атрибути.


3
Це ефективно робить те саме, що рішення Адама, але набагато більш стисло.
Даніель Мур

31
Додайте .OfType <AuthorAttribue> () до виразу замість ToDictionary, якщо вам потрібні лише атрибути Author і хочете пропустити майбутній кастинг
Адріан Занеску

2
Чи не викине це виняток, коли на одній властивості є два атрибути одного типу?
Костянтин

53

Якщо ви хочете лише одне конкретне значення атрибута, наприклад, атрибут медіа, ви можете використовувати наступний код:

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

30

Я вирішив подібні проблеми, написавши помічник атрибутів властивості загального розширення:

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

Використання:

var author = AttributeHelper.GetPropertyAttributeValue<Book, string, AuthorAttribute, string>(prop => prop.Name, attr => attr.Author);
// author = "AuthorName"

1
Як я можу отримати атрибут опису від const Fields?
Амір

1
Ви отримаєте: Помилка 1775 учасника 'Namespace.FieldName' не можна отримати з посиланням на екземпляр; кваліфікуйте його замість імені типу. Якщо вам потрібно зробити це, я пропоную змінити "const" на "readonly".
Мікаель Енгвер

1
Ви повинні мати набагато корисніший голос, ніж це, чесно. Це дуже приємна і корисна відповідь на багато випадків.
Девід Летурно

1
Дякуємо @ DavidLétourneau! Можна лише сподіватися. Здається, ви трохи допомогли в цьому.
Мікаель Енгвер

:) Як ви вважаєте, чи можливо встановити значення всіх атрибутів для одного класу за допомогою свого загального методу та призначити значення атрибута кожному властивості?
Девід Летурно

21

Ви можете використовувати GetCustomAttributesData()та GetCustomAttributes():

var attributeData = typeof(Book).GetProperty("Name").GetCustomAttributesData();
var attributes = typeof(Book).GetProperty("Name").GetCustomAttributes(false);

4
яка різниця?
Prime By Design

1
@PrimeByDesign Перший дізнається, як інстанціювати застосовані атрибути. Останнє фактично створює ці ознаки.
HappyNomad

12

Якщо ви маєте на увазі "для атрибутів, які приймають один параметр, перелічіть імена атрибутів та параметр-значення", тоді це простіше в .NET 4.5 через 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

Хоча вищезазначені відповіді безумовно спрацьовують, я б пропонував у деяких випадках використовувати дещо інший підхід.

Якщо ваш клас має декілька властивостей із завжди одним атрибутом, і ви хочете, щоб ці атрибути були відсортовані до словника, ось як:

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

Це все ще використовує кастинг, але гарантує, що виступ завжди працюватиме, оскільки ви отримаєте лише спеціальні атрибути типу "AuthorName". Якщо у вас було декілька атрибутів вище, відповіді отримали би виключення.


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

Використання:

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

або:

 //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

Ось кілька статичних методів, які можна використовувати для отримання MaxLength або будь-якого іншого атрибута.

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

}

Використання статичного методу ...

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

Або використання необов'язкового методу розширення для екземпляра ...

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

Або використовуючи повний статичний метод для будь-якого іншого атрибута (наприклад, StringLength) ...

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

Натхненний відповіддю Мікаеля Енгвера.


1

Некромантування.
Для тих, хто все ще має підтримувати .NET 2.0, або тих, хто хоче це зробити без 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);
}

Приклад використання:

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

або просто

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

У цьому прикладі я використовував DisplayName замість Author, оскільки в ньому є поле під назвою "DisplayName", яке має відображатися зі значенням.


0

щоб отримати атрибут від enum, я використовую:

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

// Використання

 var _message = Translate(code);

0

Просто шукаю потрібне місце, щоб поставити цей фрагмент коду.

скажімо, у вас є таке властивість:

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

І ви хочете отримати значення ShortName. Ви можете зробити:

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

Або зробити це загальним:

internal static string GetPropertyAttributeShortName(string propertyName)
{
    return ((DisplayAttribute)(typeof(SensorsModel).GetProperty(propertyName).GetCustomAttribute(typeof(DisplayAttribute)))).ShortName;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.