¿Cómo leo un atributo en una clase en tiempo de ejecución?

106

Estoy tratando de crear un método genérico que leerá un atributo en una clase y devolverá ese valor en tiempo de ejecución. ¿Cómo haría esto?

Nota: El atributo DomainName es de la clase DomainNameAttribute.

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

Lo que estoy tratando de generar:

//This should return "MyTable"
String DomainNameValue = GetDomainName<MyClass>();
Zaffiro
fuente
1
Enlace oficial de microsoft: msdn.microsoft.com/en-us/library/71s1zwct.aspx
Mahesh
2
Pregunta de corolario importante sobre cómo obtener todos los tipos en ensamblaje con el atributo personalizado stackoverflow.com/questions/2656189/…
Chris Marisic

Respuestas:

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

ACTUALIZAR:

Este método podría generalizarse aún más para trabajar con cualquier atributo:

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

y use así:

string name = typeof(MyClass)
    .GetAttributeValue((DomainNameAttribute dna) => dna.Name);
Darin Dimitrov
fuente
6
¡Gracias por su diligencia al responder la pregunta!
Zaffiro
1
Este método de extensión podría generalizarse aún más ampliando MemberInfo, una clase base de Type y todos, o al menos la mayoría , de los miembros de un Type. Hacerlo abriría esto para permitir la lectura de atributos de Propiedades, Campos e incluso Eventos.
M.Babcock
4
Demasiado complicado. No es necesario utilizar lambda para seleccionar el valor del atributo. Si tiene suficiente para escribir el lambda, sabe lo suficiente como para acceder al campo.
Darrel Lee
¿Cómo puedo extender este enfoque para entrar const Fileden una clase estática?
Amir
51

Ya existe una extensión para hacer esto.

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

Entonces:

var attr = typeof(MyClass).GetCustomAttribute<DomainNameAttribute>(false);
return attr != null ? attr.DomainName : "";
Darrel Lee
fuente
1
Cierto. Pero solo .NET 4.5 y versiones posteriores. Todavía estoy desarrollando código de biblioteca donde no puedo usar este método :(
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
fuente
5
Y +1 por no usar "var", por lo que es fácil entender cómo funciona.
RenniePet
No se compila. Pero "System.Reflection.MemberInfo info = typeof (MyClass) .GetTypeInfo ();" hacer
Marcel James
4

Usé la respuesta de Darin Dimitrov para crear una extensión genérica para obtener atributos de miembro para cualquier miembro de una clase (en lugar de atributos para una clase). Lo estoy publicando aquí porque otros pueden encontrarlo útil:

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

Ejemplo de uso:

//Read System.ComponentModel Description Attribute from method 'MyMethodName' in class 'MyClass'
var Attribute = typeof(MyClass).GetAttribute("MyMethodName", (DescriptionAttribute d) => d.Description);
Sevin7
fuente
La herencia no funciona en propiedades derivadas; para eso, deberá llamar a un método estático separado (System.Attribute.GetCustomAttributes) stackoverflow.com/a/7175762/184910
murraybiscuit
3

Una versión simplificada de la primera solución de Darin Dimitrov:

public string GetDomainName<T>()
{
    var dnAttribute = typeof(T).GetCustomAttribute<DomainNameAttribute>(true);
    if (dnAttribute != null)
    {
        return dnAttribute.Name;
    }
    return null;
}
jk7
fuente
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

Probablemente tan fácil de usar como el cuerpo de la función genérica en línea. No tiene ningún sentido para mí hacer que la función sea genérica sobre el tipo MyClass.

string DomainName = GetAttribute<DomainNameAttribute>(typeof(MyClass)).Name
// null reference exception if MyClass doesn't have the attribute.
Darrel Lee
fuente
0

En caso de que alguien necesite un resultado anulable y para que esto funcione en Enums, PropertyInfo y clases, así es como lo resolví. Esta es una modificación de la solución actualizada de Darin Dimitrov.

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

Ejemplos de uso:

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

En lugar de escribir mucho código, simplemente haga esto:

{         
   dynamic tableNameAttribute = typeof(T).CustomAttributes.FirstOrDefault().ToString();
   dynamic tableName = tableNameAttribute.Substring(tableNameAttribute.LastIndexOf('.'), tableNameAttribute.LastIndexOf('\\'));    
}
Naeem Ahmed
fuente
0

Cuando tenga métodos anulados con el mismo nombre, use el asistente a continuación

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

Uso: var someController = new SomeController (Algunos parámetros); var str = typeof (SomeController) .GetControllerMethodAttributeValue (x => someController.SomeMethod (It.IsAny ()), (RouteAttribute routeAttribute) => routeAttribute.Template);

Vamsi J
fuente