tags:

views:

125

answers:

2

Example:

[AttributeUsage(AttributeTargets.Class)]
public class MyAttribute : Attribute
{
  public MyAttribute()
  {
    Console.WriteLine("I was defined on the {0} class", ????);
  }
}

[MyAttribute]
public class MyClass
{
}

Can I fill in the "????" to answer this question? Do I have to iterate over every type in the list of known assemblies to do it? Or is there a shorter way?

+1  A: 
  1. You can find all the attributes applied to given type.

  2. I assume you wish to get the list of all class types decorated with MyAttribute. You must do this with Reflection I suggest. - or write an extension method on the attribute to perform this.

If you need to do what you asked in the question - I believe there's something wrong in your design because you are trying to hold the information of one particular type within the attribute that the attribute may target. Attributes are applied to any class provided they are set to target class types. So practically the attribute should not hold the information of all the types that it targets.

Though not directly related but you can check this question which I asked on SO some time back.

http://stackoverflow.com/questions/1021190/restrict-custom-attribute-so-that-it-can-be-applied-only-to-specifc-types-in-c

this. __curious_geek
aha, got the question. You're right.
Mehdi Golchin
As @curious_geek said, you should seek the loaded assemblies in the AppDomain for the types that your attribute assigned to. But, it has a pressure for seeking in the huge assemblies which are not yours. So you can create another attribute and mark your assemblies. And just seek inside them. And create a collection of MyAttributeDescriptor which describes the targeted type.
Mehdi Golchin
What I'm trying to do is build a bridge between two classes. I have a data structure and a factory structure. I want the factory to be able to say "I can make type X, and to do that use method Y". When I use this information, I'll know what type X is, and I'll want to find the appropriate factory.I know the typical approach is to invert this relationship. Type X would have an attribute saying "type Z can make me", but my data classes and factories are in separate assemblies and my factories depend on my data classes. I don't want to flip this dependency for a lot of far better reasons.
Hounshell
Right now the approach I'm planning on using is to keep a static dictionary linking Type X to the attribute, and then when I want the factory I look up the attribute in the dictionary and scan all known assemblies for the type that the attribute is assigned to. Once I find it, I cache it for future calls.
Hounshell
This may not work though. Another approach is to derive all factories from a base class. The base class' constructor could build the appropriate dictionary. Oh well, need to play with it in the morning.
Hounshell
You have to get the type of Factory in an attribute ie. FactoryAttribute(Type factoryType) which you can see that takes the factory type. All of factory class should have the same base class or interface. Then you must seek the loaded assemblies and find all of types that have FactoryAttribute. this does by another class ie. FooBuilder, it seek for the first time and cache them. Also it has another method to create an instance of the target type with its associated factory.
Mehdi Golchin
A: 

Here's a solution. Not pretty, but a solution...

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);
        }
    }
}
Mark
This is pretty much exactly what I did. Right down to the skipAssemblies.../me looks over his shoulder
Hounshell