Jak odczytać atrybut klasy w czasie wykonywania?

107

Próbuję utworzyć metodę ogólną, która odczyta atrybut klasy i zwróci tę wartość w czasie wykonywania. Jak miałbym to zrobić?

Uwaga: atrybut DomainName ma klasę DomainNameAttribute.

[DomainName("MyTable")]
Public class MyClass : DomainBase
{}

Co próbuję wygenerować:

//This should return "MyTable"
String DomainNameValue = GetDomainName<MyClass>();
Zaffiro
źródło
1
Oficjalny link do firmy Microsoft: msdn.microsoft.com/en-us/library/71s1zwct.aspx
Mahesh
2
Ważne pytanie uzupełniające, jak uzyskać wszystkie typy w zestawie z atrybutem niestandardowym stackoverflow.com/questions/2656189/ ...
Chris Marisic

Odpowiedzi:

236
public string GetDomainName<T>()
{
    var dnAttribute = typeof(T).GetCustomAttributes(
        typeof(DomainNameAttribute), true
    ).FirstOrDefault() as DomainNameAttribute;
    if (dnAttribute != null)
    {
        return dnAttribute.Name;
    }
    return null;
}

AKTUALIZACJA:

Tę metodę można dodatkowo uogólnić, aby działała z dowolnym atrybutem:

public static class AttributeExtensions
{
    public static TValue GetAttributeValue<TAttribute, TValue>(
        this Type type, 
        Func<TAttribute, TValue> valueSelector) 
        where TAttribute : Attribute
    {
        var att = type.GetCustomAttributes(
            typeof(TAttribute), true
        ).FirstOrDefault() as TAttribute;
        if (att != null)
        {
            return valueSelector(att);
        }
        return default(TValue);
    }
}

i użyj w ten sposób:

string name = typeof(MyClass)
    .GetAttributeValue((DomainNameAttribute dna) => dna.Name);
Darin Dimitrov
źródło
6
Dziękuję za staranność w udzieleniu odpowiedzi na pytanie!
Zaffiro
1
Ta metoda rozszerzenia może być dodatkowo uogólniona przez rozszerzenie MemberInfo, klasy bazowej Type i wszystkich - lub przynajmniej większości - elementów członkowskich Type. W ten sposób otworzyłoby się to, aby umożliwić odczyt atrybutów z właściwości, pól, a nawet zdarzeń.
M.Babcock,
4
Zbyt skomplikowane. Nie ma potrzeby używania lambda do wybierania wartości atrybutu. Jeśli wystarczy napisać lambdę, wiesz wystarczająco dużo, aby uzyskać dostęp do pola.
Darrel Lee
Jak mogę rozszerzyć to podejście, aby uzyskać const Filedklasę statyczną?
Amir
51

Istnieje już rozszerzenie, które to umożliwia.

namespace System.Reflection
{
    // Summary:
    //     Contains static methods for retrieving custom attributes.
    public static class CustomAttributeExtensions
    {
        public static T GetCustomAttribute<T>(this MemberInfo element, bool inherit) where T : Attribute;
    }
}

Więc:

var attr = typeof(MyClass).GetCustomAttribute<DomainNameAttribute>(false);
return attr != null ? attr.DomainName : "";
Darrel Lee
źródło
1
Prawdziwe. Ale tylko .NET 4.5 i nowsze. Wciąż rozwijam kod biblioteki, w której nie mogę użyć tej metody :(
andreas
15
System.Reflection.MemberInfo info = typeof(MyClass);
object[] attributes = info.GetCustomAttributes(true);

for (int i = 0; i < attributes.Length; i++)
{
    if (attributes[i] is DomainNameAttribute)
    {
        System.Console.WriteLine(((DomainNameAttribute) attributes[i]).Name);
    }   
}
Merritt
źródło
5
I +1 za nieużywanie „zmienna”, więc łatwo zrozumieć, jak to działa.
RenniePet
Nie kompiluje się. Ale "System.Reflection.MemberInfo info = typeof (MyClass) .GetTypeInfo ();" zrobić
Marcel James
4

Użyłem odpowiedzi Darina Dimitrova, aby utworzyć ogólne rozszerzenie, aby uzyskać atrybuty elementu członkowskiego dla dowolnego członka w klasie (zamiast atrybutów dla klasy). Publikuję to tutaj, ponieważ inni mogą uznać to za przydatne:

public static class AttributeExtensions
{
    /// <summary>
    /// Returns the value of a member attribute for any member in a class.
    ///     (a member is a Field, Property, Method, etc...)    
    /// <remarks>
    /// If there is more than one member of the same name in the class, it will return the first one (this applies to overloaded methods)
    /// </remarks>
    /// <example>
    /// Read System.ComponentModel Description Attribute from method 'MyMethodName' in class 'MyClass': 
    ///     var Attribute = typeof(MyClass).GetAttribute("MyMethodName", (DescriptionAttribute d) => d.Description);
    /// </example>
    /// <param name="type">The class that contains the member as a type</param>
    /// <param name="MemberName">Name of the member in the class</param>
    /// <param name="valueSelector">Attribute type and property to get (will return first instance if there are multiple attributes of the same type)</param>
    /// <param name="inherit">true to search this member's inheritance chain to find the attributes; otherwise, false. This parameter is ignored for properties and events</param>
    /// </summary>    
    public static TValue GetAttribute<TAttribute, TValue>(this Type type, string MemberName, Func<TAttribute, TValue> valueSelector, bool inherit = false) where TAttribute : Attribute
    {
        var att = type.GetMember(MemberName).FirstOrDefault().GetCustomAttributes(typeof(TAttribute), inherit).FirstOrDefault() as TAttribute;
        if (att != null)
        {
            return valueSelector(att);
        }
        return default(TValue);
    }
}

Przykład użycia:

//Read System.ComponentModel Description Attribute from method 'MyMethodName' in class 'MyClass'
var Attribute = typeof(MyClass).GetAttribute("MyMethodName", (DescriptionAttribute d) => d.Description);
Sevin7
źródło
Dziedziczenie nie działa na właściwościach pochodnych - w tym celu musisz wywołać oddzielną metodę statyczną (System.Attribute.GetCustomAttributes) stackoverflow.com/a/7175762/184910
murraybiscuit
3

Uproszczona wersja pierwszego rozwiązania Darina Dimitrova:

public string GetDomainName<T>()
{
    var dnAttribute = typeof(T).GetCustomAttribute<DomainNameAttribute>(true);
    if (dnAttribute != null)
    {
        return dnAttribute.Name;
    }
    return null;
}
jk7
źródło
0
' Simplified Generic version. 
Shared Function GetAttribute(Of TAttribute)(info As MemberInfo) As TAttribute
    Return info.GetCustomAttributes(GetType(TAttribute), _
                                    False).FirstOrDefault()
End Function

' Example usage over PropertyInfo
Dim fieldAttr = GetAttribute(Of DataObjectFieldAttribute)(pInfo)
If fieldAttr IsNot Nothing AndAlso fieldAttr.PrimaryKey Then
    keys.Add(pInfo.Name)
End If

Prawdopodobnie równie łatwy w użyciu korpus funkcji ogólnych w treści. Nie ma dla mnie żadnego sensu, aby funkcja była ogólna dla typu MyClass.

string DomainName = GetAttribute<DomainNameAttribute>(typeof(MyClass)).Name
// null reference exception if MyClass doesn't have the attribute.
Darrel Lee
źródło
0

Na wypadek, gdyby ktoś potrzebował wyniku dopuszczającego wartość null i aby działał on między Enums, PropertyInfo i klasami, oto jak to rozwiązałem. To modyfikacja zaktualizowanego rozwiązania Darina Dimitrova.

public static object GetAttributeValue<TAttribute, TValue>(this object val, Func<TAttribute, TValue> valueSelector) where TAttribute : Attribute
{
    try
    {
        Type t = val.GetType();
        TAttribute attr;
        if (t.IsEnum && t.GetField(val.ToString()).GetCustomAttributes(typeof(TAttribute), true).FirstOrDefault() is TAttribute att)
        {
            // Applies to Enum values
            attr = att;
        }
        else if (val is PropertyInfo pi && pi.GetCustomAttributes(typeof(TAttribute), true).FirstOrDefault() is TAttribute piAtt)
        {
            // Applies to Properties in a Class
            attr = piAtt;
        }
        else
        {
            // Applies to classes
            attr = (TAttribute)t.GetCustomAttributes(typeof(TAttribute), false).FirstOrDefault();
        }
        return valueSelector(attr);
    }
    catch
    {
        return null;
    }
}

Przykłady użycia:

// Class
SettingsEnum.SettingGroup settingGroup = (SettingsEnum.SettingGroup)(this.GetAttributeValue((SettingGroupAttribute attr) => attr.Value) as SettingsEnum.SettingGroup?);

// Enum
DescriptionAttribute desc = settingGroup.GetAttributeValue((DescriptionAttribute attr) => attr) as DescriptionAttribute;

// PropertyInfo       
foreach (PropertyInfo pi in this.GetType().GetProperties())
{
    string setting = ((SettingsEnum.SettingName)(pi.GetAttributeValue((SettingNameAttribute attr) => attr.Value) as SettingsEnum.SettingName?)).ToString();
}
Mideus
źródło
0

Zamiast pisać dużo kodu, po prostu zrób to:

{         
   dynamic tableNameAttribute = typeof(T).CustomAttributes.FirstOrDefault().ToString();
   dynamic tableName = tableNameAttribute.Substring(tableNameAttribute.LastIndexOf('.'), tableNameAttribute.LastIndexOf('\\'));    
}
Naeem Ahmed
źródło
0

Jeśli masz nadpisane metody o tej samej nazwie, użyj poniższego pomocnika

public static TValue GetControllerMethodAttributeValue<T, TT, TAttribute, TValue>(this T type, Expression<Func<T, TT>> exp, Func<TAttribute, TValue> valueSelector) where TAttribute : Attribute
        {
            var memberExpression = exp?.Body as MethodCallExpression;

            if (memberExpression.Method.GetCustomAttributes(typeof(TAttribute), false).FirstOrDefault() is TAttribute attr && valueSelector != null)
            {
                return valueSelector(attr);
            }

            return default(TValue);
        }

Sposób użycia: var someController = new SomeController (Some params); var str = typeof (SomeController) .GetControllerMethodAttributeValue (x => someController.SomeMethod (It.IsAny ()), (RouteAttribute routeAttribute) => routeAttribute.Template);

Vamsi J
źródło