.NET 2.0 ReflectionOnly context

I am preparing a new series of talks on What's New in .NET 2.0 for several customers.  One of the topics that a customer requested is "What's New in Reflection".  I pulled up Joel Pobar's post on What's new in System.Reflection (and friends), and the ReflectionOnly context struck me as really interesting.  Joel's post indicates that you can now reflect over custom attributes without firing the constructor. 

My first attempt was to use Type.GetCustomAttributes, just like we did in .NET 1.1.  I was greeted with an exception:

"It is illegal to reflect on the custom attributes of a Type loaded via ReflectionOnlyGetType (see Assembly.ReflectionOnly) -- use CustomAttributeData instead."

Of course, this makes sense after I figured out how to fix it.  GetCustomAttributes will instantiate an instance of the custom attribute to determine the values that were applied to it, which is precisely the behavior that we are trying to avoid for this example.  I went spelunking looking for what in the world CustomAttributeData is and what it does, and turned up this description from the online help:

Provides access to custom attribute data for assemblies, modules, types, members and parameters that are loaded into the reflection-only context.

[via MSDN]

Booyah!  Using the CustomAttributeData type, you can inspect without actually loading the type.  Further, this makes it simple to differentiate between constructor arguments (typically implemented when a value is required) and named arguments (typically implemented for optional values) for custom attributes.  Here's my example:

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.Generic;

namespace WhidbeyReflection
{
    [Contoso("foo", Value = "bar")]
    class Class1
    {
        static void Main(string[] args)
        {

            //Old way... Get custom attributes and fire constructor

            Type t = typeof(WhidbeyReflection.Class1);
            ContosoAttribute[] attributes = (ContosoAttribute[])t.GetCustomAttributes(typeof(ContosoAttribute),false);
            foreach (ContosoAttribute attribute in attributes)
            {
                Console.WriteLine(attribute.Title + " " + attribute.Value);
            }

            //New in .NET 2.0... get custom attributes without firing constructor
            t = Type.ReflectionOnlyGetType("WhidbeyReflection.Class1, WhidbeyReflection", true, false);
            ShowAttributeData(CustomAttributeData.GetCustomAttributes(t));

        }

        private static void ShowAttributeData(IList<CustomAttributeData> attributes)
        {
            foreach (CustomAttributeData cad in attributes)
            {
                Console.WriteLine("   {0}", cad);
                Console.WriteLine("      Constructor: {0}", cad.Constructor);

                Console.WriteLine("      Constructor arguments:");
                foreach (CustomAttributeTypedArgument cata
                    in cad.ConstructorArguments)
                {
                    Console.WriteLine("         Type: {0} Value: {1}",
                        cata.ArgumentType, cata.Value);
                }

                Console.WriteLine("      Named arguments:");
                foreach (CustomAttributeNamedArgument cana
                    in cad.NamedArguments)
                {
                    CustomAttributeTypedArgument cata = cana.TypedValue;
                    Console.WriteLine("         MemberInfo: {0}",
                        cana.MemberInfo);
                    Console.WriteLine("         Type: {0} Value: {1}",
                        cata.ArgumentType, cata.Value);
                }
            }

        }
    }

    [AttributeUsage(AttributeTargets.Class,AllowMultiple=false)]
    class ContosoAttribute : Attribute
    {
        private string _title;
        private string _value;

        public ContosoAttribute(string title)
        {
            _title = title;
        }

        //Constructor argument
        public string Title
        {
            get { return _title; }
        }

        //Named argument
        public string Value
        {
            get { return _value; }
            set { _value = value; }
        }
    }

}

The ouput:

   foo bar

   [WhidbeyReflection.ContosoAttribute("foo", Value = "bar")]
      Constructor: Void .ctor(System.String)
      Constructor arguments:
         Type: System.String Value: foo
      Named arguments:
         MemberInfo: System.String Value
         Type: System.String Value: bar