2010-02-21 15 views
41

Quiero acceder a las propiedades de una clase desde la clase de atributo mediante el uso de la reflexión. ¿Es posible?¿Pueden los atributos de C# acceder a la clase de destino?

Por ejemplo:

class MyAttribute : Attribute 
{ 
    private void AccessTargetClass() 
    { 
     // Do some operations 
    } 
} 

[MyAttribute] 
class TargetClass 
{ 
} 

Respuesta

15
No

directamente, pero para que el atributo de hacer nada, ya debe tener un identificador para el tipo/miembro y llamar a su método GetCustomAttributes, por lo que simplemente puede pasar desde allí:

class MyAttribute : Attribute 
{ 
    public void DoSomethingWithDeclaringType(Type t) 
    { 
    } 
} 

su uso de este modo:

Type classType = typeof(MyClass); 
object[] attribs = classType.GetCustomAttributes(typeof(MyAttribute), true); 
if(attribs.Length > 0) 
{ 
    ((MyAttribute)attribs[0]).DoSomethingWithDeclaringType(classType); 
} 
+2

También podría proporcionar el tipo de declaración en el constructor de atributos, que le da acceso a ese tipo. P.ej. ' clase MyAttribute: Atributo { privada vacío MyAttribute (Tipo declaringType) {// acceso declarar escribir aquí, incluso mantener una referencia si es necesario. } } [MyAttribute (typeof (TargetClass))] clase TargetClass { } ' – Jaans

18

allí ar e casos en los que no puede confiar en el código de llamada para pasar una referencia de tipo.

A continuación se muestra una solución que cociné para resolver este problema. Es un poco de un enfoque de escopeta, pero funciona ...

using System; 
using System.Reflection; 
using System.Collections.Generic; 
using System.Linq; 
using System.Diagnostics; 


namespace Ethica.Reflection 
{ 
    /// <summary> 
    /// Helps in discovery of the target of an Attribute 
    /// </summary> 
    /// <typeparam name="TAttribute">An Attribute derived type</typeparam> 
    /// <remarks> 
    /// The .NET framework does not provide navigation from attributes back to their targets, principally for the reason that 
    /// in typical usage scenarios for attributes, the attribute is discovered by a routine which already has a reference to a 
    /// member type. 
    /// 
    /// There are, however, bona-fide cases where an attribute needs to detect it's target - an example is a localizable sub-class of the 
    /// DescriptionAttribute. In order for the DescriptionAttribute to return a localized string, it requires a resource key and, ideally, 
    /// a type reference as the base-key for the ResourceManager. A DescriptionAttribute could not provide this information without 
    /// a reference to it's target type. 
    /// 
    /// Note to callers: 
    /// 
    /// Your Attribute-derived class must implement Equals and GetHashCode, otherwise a run-time exception will occur, since this class 
    /// creates a dictionary of attributes in order to speed up target lookups. 
    /// </remarks> 
    public static class AttributeTargetHelper<TAttribute> 
     where TAttribute : Attribute 
    { 
     /// <summary> 
     /// Map of attributes and their respective targets 
     /// </summary> 
     private static Dictionary<TAttribute, object> targetMap; 

     /// <summary> 
     /// List of assemblies that should not be rescanned for types. 
     /// </summary> 
     private static List<string> skipAssemblies; 

     /// <summary> 
     /// Adds an attribute and it's target to the dictionary 
     /// </summary> 
     /// <param name="attribute"></param> 
     /// <param name="item"></param> 
     private static void Add(TAttribute attribute, object item) 
     { 
      targetMap.Add(attribute, item); 
     } 

     /// <summary> 
     /// Scans an assembly for all instances of the attribute. 
     /// </summary> 
     /// <param name="assembly"></param> 
     private static void ScanAssembly(Assembly assembly) 
     { 
      const BindingFlags memberInfoBinding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance; 

      if (!skipAssemblies.Contains(assembly.FullName)) 
      { 
       skipAssemblies.Add(assembly.FullName); 

       Debug.WriteLine("Loading attribute targets for " + typeof(TAttribute).Name + " from assembly " + assembly.FullName); 

       foreach (TAttribute attr in assembly.GetCustomAttributes(typeof(TAttribute), false)) 
        Add(attr, assembly); 

       foreach (Type type in assembly.GetTypes()) 
       { 
        foreach (TAttribute attr in type.GetCustomAttributes(typeof(TAttribute), false)) 
         Add(attr, type); 

        foreach (MemberInfo member in type.GetMembers(memberInfoBinding)) 
        { 
         foreach (TAttribute attr in member.GetCustomAttributes(typeof(TAttribute), false)) 
          Add(attr, member); 

         if (member.MemberType == MemberTypes.Method) 
          foreach (var parameter in ((MethodInfo)member).GetParameters()) 
           foreach (TAttribute attr in parameter.GetCustomAttributes(typeof(TAttribute), false)) 
            Add(attr, parameter); 
        } 
       } 
      } 

      foreach (var assemblyName in assembly.GetReferencedAssemblies()) 
      { 
       if (!skipAssemblies.Contains(assemblyName.FullName)) 
        ScanAssembly(Assembly.Load(assemblyName)); 
      } 
     } 

     /// <summary> 
     /// Returns the target of an attribute. 
     /// </summary> 
     /// <param name="attribute">The attribute for which a target is sought</param> 
     /// <returns>The target of the attribute - either an Assembly, Type or MemberInfo instance.</returns> 
     public static object GetTarget(TAttribute attribute) 
     { 
      object result; 
      if (!targetMap.TryGetValue(attribute, out result)) 
      { 
       // Since types can be loaded at any time, recheck that all assemblies are included... 
       // Walk up the stack in a last-ditch effort to find instances of the attribute. 
       StackTrace stackTrace = new StackTrace();   // get call stack 
       StackFrame[] stackFrames = stackTrace.GetFrames(); // get method calls (frames) 

       // write call stack method names 
       foreach (StackFrame stackFrame in stackFrames) 
       { 
        Console.WriteLine(stackFrame.GetMethod().Name); // write method name 
        ScanAssembly(stackFrame.GetMethod().GetType().Assembly); 
       } 

       if (!targetMap.TryGetValue(attribute, out result)) 
        throw new InvalidProgramException("Cannot find assembly referencing attribute"); 
      } 
      return result; 
     } 

     /// <summary> 
     /// Static constructor for type. 
     /// </summary> 
     static AttributeTargetHelper() 
     { 
      targetMap = new Dictionary<TAttribute, object>(); 

      // Do not load any assemblies reference by the assembly which declares the attribute, since they cannot possibly use the attribute 
      skipAssemblies = new List<string>(typeof(TAttribute).Assembly.GetReferencedAssemblies().Select(c => c.FullName)); 

      // Skip common system assemblies 
      skipAssemblies.Add("System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); 
      skipAssemblies.Add("System.Security, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); 
      skipAssemblies.Add("System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); 
      skipAssemblies.Add("System.Data.SqlXml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); 
      skipAssemblies.Add("System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); 
      skipAssemblies.Add("System.Numerics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); 

      // Scan the entire application 
      ScanAssembly(Assembly.GetEntryAssembly()); 
     } 

    } 


    /// <summary> 
    /// Extends attributes so that their targets can be discovered 
    /// </summary> 
    public static class AttributeTargetHelperExtension 
    { 
     /// <summary> 
     /// Gets the target of an attribute 
     /// </summary> 
     /// <typeparam name="TAttribute"></typeparam> 
     /// <param name="attribute">The attribute for which a target is sought</param> 
     /// <returns>The target of the attribute - either an Assembly, Type or MemberInfo instance.</returns> 
     public static object GetTarget<TAttribute>(this TAttribute attribute) 
      where TAttribute : Attribute 
     { 
      return AttributeTargetHelper<TAttribute>.GetTarget(attribute); 
     } 
    } 
} 
+0

Gracias por la solución – Heka

12

Atributos propósito principal es la anotación: se declara? esa clase/método/propiedad debe tratarse de esta o aquella manera, pero el código que realmente analiza todo eso debe estar en otro lugar. Entonces su intención de hacer algo dentro de un atributo está rompiendo este patrón. Mi consejo: considera otra solución.

+0

El enfoque de esperar hasta que el tipo "DoSomethingWithDeclaringType" de las obras, sino que significa de alguna manera hacer cumplir que el la verificación solo se realiza en la primera llamada a DoSomethingWithDeclaringType, y significa asegurarse de que nadie busque directamente el atributo y haga algo con él sin pasar por la función de aprobación. Preferiría poner este código de validación en el atributo en sí, si pudiera, y no creo que eso viole el patrón si fuera posible. – DMC

+0

Supongamos que un atributo establece una relación entre una clase y otra. ¿Dónde colocas el código de validación que dice "Sí, estas dos clases son realmente compatibles entre sí?" Solo quiero ejecutar ese código una vez en el momento de la carga, cuando se establece el atributo, y no quiero confiar en cada clase que agrega el atributo para asegurarse de que ingresen el código para validar la conexión. El método de esperar hasta que funcione el tipo "DoSomethingWithDeclaringType", pero de alguna manera implica que la verificación solo se realiza en la primera llamada a DoSomethingWithDeclaringTyp – DMC

Cuestiones relacionadas