Thursday, 9 June 2011

Type discovery in Assemblies using Attributes

I thought I would share a useful class I wrote a long time ago that I used to load a file from the disc, and assuming it was a .Net assembly would identify all the Types in the file which were decorated with a specific attribute.

Unfortunately, and surprisingly the Assembly.ReflectionOnlyLoadFrom(string filename) method also locks files, which was a surprise.... but Jeffry Richter explains the reasoning behind this in his book "CLR via C#". The reason is that the loaded assembly may contain types which derive from types in a different assembly..... So I had to fall back on the strategy of loading the assembly into a temporary application domain, reflect on the assembly and unload the application domain thus unlocking the file and unloading it from memory.

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

namespace AssemblyDiscoveryConsole
{
    // The attribute class you are looking for within external assemblies
    public class SomeAttribute : Attribute
    {
    }

    public class TypeFinder
    {
        public string[] LoadTypes(string assemblyFilename)
        {
            AppDomain appDomain = AppDomain.CreateDomain("TypeFinder");
            try
            {
                // Load and query asseblies in domain and return results
                AssemblyReflector ar = (AssemblyReflector)appDomain.CreateInstanceAndUnwrap(
                    typeof(AssemblyReflector).Assembly.FullName,
                    typeof(AssemblyReflector).FullName);
                return ar.LoadTypes(assemblyFilename);
            }
            finally
            {
                // unload the temporary domain
                AppDomain.Unload(appDomain);
            }
        }

        /// <summary>
        /// Class used to load and query assemblies within "TypeFinder" Domain
        /// </summary>
        private class AssemblyReflector : MarshalByRefObject
        {
            private IEnumerable<string> EnumerateTypes(string assemblyFilename)
            {
                Assembly asm = Assembly.LoadFile(assemblyFilename);
                if (asm == null) throw new Exception("Assembly not found");
                foreach (Type t in asm.GetTypes())
                {
                    if (t.GetCustomAttributes(typeof(SomeAttribute), true).FirstOrDefault() != null)
                    {
                        yield return t.AssemblyQualifiedName;
                    }
                }
            }
            public string[] LoadTypes(string assemblyFilename)
            {
                return EnumerateTypes(assemblyFilename).ToArray();
            }
        }
    }
}