Hi folks - long time no see... E.L.D.S.? Yeah, sure! Never mind that, here is a dump of my understanding of the TypeDescriptor. Hope you find it useful!

What is Metadata in the context of CLR? Read all about it here. And what makes this concept really powerful is that any code can access the CLR metadata engine (using managed and unmanaged API) to inspect any given managed code down to each IL instruction. The CLR metadata engine also has API to let other code emit managed code. But once emitted and baked, the CLR metadata engine will only let you inspect the managed code – you will not be able to extend or modify any of it.

Now we all know that the all-powerful and mighty .NET Framework Designtime infrastructure lets every component have 2 views of itself – a designtime view and a runtime view. You might have also observed that the designtime view is not quiet the same as the runtime view. If you haven’t, consider the following:

  1. Every Form on the design surface of a Visual Studio 2005 DeviceApplication project has a property called FormFactor. Can you locate it in System.Windows.Forms.dll using Reflector or ILDAsm or the Reflection API?
  2. If the ErrorProvider component is present on the design surface, all other controls on the design surface have new property called Error on errorProvider1. Can you locate this property in System.Windows.Forms.dll?

This means that the designtime infrastructure is displaying a metadata different from what is returned by reflection API. Clearly someone is modifying the metadata before the designtime infrastructure displays it. Now who would that be?

The System.ComponentModel.TypeDescriptor of course!

TypeDescriptor is a metadata engine provided by the .NET FCL. The MSDN documentation says the following:

... In contrast, TypeDescriptor is an extensible inspection mechanism for components: those classes that implement the IComponent interface...

That is not quiet correct. TypeDescriptor is an extensible inspection mechanism not just for components but for all Types and for individual instances of any given Type. Although if the target is a component, it may have a Site and the Site may proffer services, e.g. the ITypeDescriptorFilterService (discussed below) or the IExtenderProvider, which can further enhance the extensibility of the TypeDescriptor.

To restate: Unlike the native CLR metadata engine, TypeDescriptor lets you inspect as well as modify the metadata (add, change and delete) of the target in any conceivable manner. The term target, for the rest of this text will refer to an element of the set of all .NET Framework Types (including Types imported from COM) and all instances of every .NET Framework Type.

Note that an update to a Type’s metadata is visible for the Type and all its instances. But when the metadata of an instance of any Type is updated, the update is seen only for that instance. Metadata of the Type and its other instances are unaffected.

Also note that TypeDescriptor and Reflection API do not interoperate. While TypeDescriptor gets the initial metadata information using Reflection (explained below), further changes to metadata using TypeDescriptor is not visible using Reflection. Only the TypeDescriptor API will let you inspect the new metadata and update it further.

Here is an example of metadata inspection using TypeDescriptor:

using System;
using System.ComponentModel;

namespace InspectWithTD
{
   
class Program
    {
       
static void Main(string[] args)
        {
           
Console.WriteLine("Attributes on System.Object are:");
           
AttributeCollection attributes = TypeDescriptor.GetAttributes(typeof(object));
           
foreach (Attribute attribute in attributes) Console.WriteLine("- " + attribute.GetType().Name);

           
Console.WriteLine("\nProperties of a string object are:");
           
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties("A String");
           
foreach (PropertyDescriptor property in properties) Console.WriteLine("- " + property.Name);

           
Console.WriteLine("\nEvents on the AppDomain we are running in are:");
           
EventDescriptorCollection events = TypeDescriptor.GetEvents(AppDomain.CurrentDomain);
           
foreach (EventDescriptor @event in events) Console.WriteLine("- " + @event.Name);
        }
    }
}

In the following sections we will take a brief look at the architecture of the TypeDescriptor. Then we see what its capabilities are. And finally close the topic with an example. But before we get started, I suggest you open System.dll in Reflector, navigate to TypeDescriptor and disassemble the method implementations to get a deeper understanding.

Brief Architecture of TypeDescriptor

Internally, the TypeDescriptor creates (on demand) and maintains a stack of TypeDescriptionProvider (provider hereafter) for each target. Providers can be considered as plugins for the TypeDescriptor and they are the entities responsible for supplying the target’s metadata and for most other TypeDescriptor magic.

For Type targets, the TypeDescriptor can initialize the provider stack with a read-only provider that is built on Reflection API. Or with a delegating provider that is hooked up to delegate the calls each time to the latest provider for the Type’s base Type. This way the calls are handled by the latest provider for the Type’s base Type, if the Type doesn’t have provider stack by itself.

For instance targets, the provider stack can be the same as the provider stack of the instance’s Type. Or it can be initialized with a delegating provider that is hooked up to delegate the calls each time to the latest provider for the instance’s Type. This way the calls are handled by the latest provider for the instance’s Type, if the instance doesn’t have provider stack by itself.

Once the provider stack is initialized, the TypeDescriptor lets you add/remove custom providers to/from the stack. Custom providers need to extend TypeDescriptionProvider and can have a parent provider. They can selectively change the TypeDescriptor behavior for a given target by selectively overriding the TypeDescriptionProvider methods. If a parent provider exists, the methods of TypeDescriptionProvider delegate the work to the parent provider. This is how we achieve TypeDescriptionProvider chaining. The provider one on the top of the target’s provider stack (latest provider hereafter) is returned by TypeDescriptor.GetProvider and is the one doing most of the TypeDescriptor’s work as we are going to see below.

To proceed further we must understand the ICustomTypeDescriptor (custom type descriptor or CTD hereafter). Click on the link and see its API – isn’t there a striking similarity with its methods and metadata inspection methods of the TypeDescriptor? Indeed – the purpose of a CTD is to return the metadata for the given target. The default implementation of ICustomTypeDescriptor, the CustomTypeDescriptor, takes in a parent ICustomTypeDescriptor (to enable CustomTypeDescriptor chaining) in its constructor. If the parent exists, each method of the CustomTypeDescriptor simply delegates the call to the parent – else the methods return empty metadata.

TypeDescriptionProvider.GetTypeDescriptor returns the CTD supplied by a target’s provider. For instance targets another method, TypeDescriptionProvide.GetExtendedTypeDescriptor also returns a CTD. But this CTD implementation returns the metadata contributed to the instance by the Extender Providers. The default implementation of this returns empty metadata if the instance is not a component or it is not sited. Otherwise if the target is sited and the site proffers the IExtenderListService, which lists the extenders. If the site doesn’t proffer the service, the extenders in the target’s container are listed. From the list of extenders, the extended metadata is collected.

The TypeDescriptor methods that return the target’s metadata are in fact wrappers around the corresponding methods of the CTD obtained from the target’s latest provider. For Types, calling GetTypeDescriptor on the latest provider obtains the CTD. However obtaining the CTD for an instance is a little involved since the instance itself may implement ICustomTypeDescriptor. If it does, then the CTD returned is a merge of the ICustomTypeDescriptor implemented by the instance and the CTD supplied by the latest provider of the instance – but with a preference to the ICustomTypeDescriptor implemented by the instance. If the target doesn’t implement ICustomTypeDescriptor then the CTD obtained is the one supplied by the instance’s latest provider. This means any arbitrary object, by implementing ICustomTypeDescriptor, can dynamically modify the metadata obtained from it by the TypeDescriptor. Of course many of the TypeDescriptor method take in a boolean argument – which when true, simply tells the TypeDescriptor to ignore the instance’s implementation of ICustomTypeDescriptor.

A few of the above methods that return metadata of instances are a bit more sophisticated. For instance the TypeDescriptor.GetProperties that takes in an instance, determines the set of properties to return in as many as 4 successive stages. The output of each stage is fed to the next stage.

  1. First stage: The CTD is obtained (as above) from the latest provider and it returns the initial set of properties.
  2. Second stage: The input set is merged with the set of properties contributed by the Extender Providers. To obtain this set, GetExtendedTypeDescriptor is called on the latest provider for the instance.
  3. Third stage: If the component is sited and the site proffers the ITypeDescriptorFilterService, the input set is passed to the ITypeDescriptorFilterService to be filtered. This forms the basis of the IDesignerFilter that allows a ComponentDesigner to add/change/delete properties from the set of properties of the component it is designing.
  4. Fourth stage: The input set is filtered based on a set of attributes. The rules used for this filtering is explained well in the Remarks section here.

Whew! That is sophistication for you. The power you wield is just awesome! The above is for GetProperties method with instance target. The logic is similar for TypeDescriptor.GetAttributes and TypeDescriptor.GetEvents methods with instance target. Of course, the metadata obtained from the above stages are cached into the IDictionaryService if the instance happens to be a component and its site proffers the dictionary service. This way, unless the cache is invalid, the subsequent call to obtain the metadata can work off the cache. Note that this is a per-instance cache, unlike the per-Type cache used by TypeDescriptor methods that work on Types. The necessity of a per-object cache is explained well in the Remarks section here. To obtain the per-instance cache, call the GetCache on the latest provider. To clear the per-type and the per-object caches call TypeDescriptor.Refresh. Once the caches are cleared, the TypeDescriptor will rebuild all of them on subsequent calls.

We have seen how we can write a TypeDescriptionProvider and a CustomTypeDescriptor for a given target and return any metadata we choose – say a completely different set from what is returned by Reflection. Now, what if someone wants to call some of the lower level Reflection API on the target to get more information about the target? E.g. say the target is object1 and using TypeDescriptor.GetProperties(object1) we get the Pubic, DeclaredOnly and Instance properties of object1. This set of properties is different from the set obtained by object1.GetType().GetProperties() with the above BindingFlags. We have an inconsistency a.k.a. confusion and this is not a happy situation!  To avoid such scenarios, you should use TypeDescriptor.GetReflectionType(object1).GetProperties() with the above BindingFlags. Internally this method will return the System.Type implementation returned by the GetReflectionType call on the object1’s provider. So if you are greatly mucking around with the metadata, make sure your provider’s GetReflectionType returns a System.Type implementation that returns metadata consistent with the provider’s custom type descriptor. This is exactly what happens in the VSD designers where we are actually using a desktop type (e.g. desktop WinForms Button) to represent the corresponding device type (the NETCF Button) on the design surface. We will delve deeper into this in a future post. Also for another illustration, refer to Brian Pepin’s post on designing abstract forms.

As if this much power isn’t enough, the TypeDescriptor.CreateInstance, takes all of the above a step further. CreateInstance first checks if the ServiceProvider (first argument) proffers a TypeDescriptionProvider and if so, it delegates the call to this provider. Else the provider for the target is located the call is delegated to its CreateInstance. The provider can create and return any instance of any other Type (this is called Instance Substitution). Refer to Brian’s post mentioned above to see application of this. Another application of this is in the VSD designers, where we implement a provider that overrides the CreateInstance calls to hook in Type Filtering into the designtime – this is the trick that filters a desktop Type such that on the design surface it appears as the corresponding device (.NETCF) Type.  More about these in the same future post I promised above.

Capabilities of the TypeDescriptor

The purpose of all of the above blah, blah, yada, yada... is so that you can better understand the capabilities of the TypeDescriptor. A great set of documentation on is already provided on MSDN2 that lists and explains all of them – so read it carefully and understand it and let me know if some parts are still not clear. Understanding the TypeDescriptor is critical to understanding my future posts on Designers. To restate, the following are the capabilities of the TypeDescriptor:

Capability Description
Instance substitution Enables an arbitrary type to be created when another type is requested.
Metadata substitution Enables an object's metadata to be modified.
Attribute redirection Enables attributes to be specified dynamically.
Target substitution and shadowing Enables one object to stand in for another.
Extended type descriptor support Enables access to object properties added by other objects.

A simple illustration...

Let us now close this discussion with a small example. It doesn’t do anything useful other than illustrating that you can add your choice of metadata to any given .NET object/Type. We ask TypeDescriptor to register a custom provider the System.Object. And from the above you can see why this provider automatically becomes the provider for all .NET objects and Types. Our custom descriptor than returns a custom type descriptor which when asked for properties, just creates a custom property and returns it along with the original set of properties. Note that both our custom provider and type descriptor selectively override 1 method each and using chaining they delegate the remaining calls to their respective parents.

using System;
using System.ComponentModel;
using System.Collections.Generic;

namespace ExtendWithTD
{
   
class Program
    {
       
static void Main(string[] args)
        {
           
Console.WriteLine("Properties of an arbitrary object: Console.Out:");
           
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(Console.Out);
           
foreach (PropertyDescriptor property in properties) Console.WriteLine("- " + property.Name);

           
Console.WriteLine("\nAdd our useless provider System.Object and chain it with the original one...");
           
TypeDescriptor.AddProvider(new UselessTypeDescriptionProvider(TypeDescriptor.GetProvider(typeof(object))), typeof(object));

           
Console.WriteLine("\nProperties of an arbitrary object: Console.Out:");
            properties
= TypeDescriptor.GetProperties(Console.Out);
           
foreach (PropertyDescriptor property in properties) Console.WriteLine("- " + property.Name);
        }
    }

   
/// <summary>
    /// This is our custom provider. It simply provides a custom type descriptor
    /// and delegates all its other tasks to its parent
    /// </summary>
    internal sealed class UselessTypeDescriptionProvider : TypeDescriptionProvider
    {
       
/// <summary>
   
    /// Constructor
        /// </summary>
        internal UselessTypeDescriptionProvider(TypeDescriptionProvider parent)
            :
base(parent)
        {
        }

       
/// <summary>
        /// Create and return our custom type descriptor and chain it with the original
   
    /// custom type descriptor
   
    /// </summary>
   
    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
        {
           
return new UselessCustomTypeDescriptor(base.GetTypeDescriptor(objectType, instance));
        }
    }

   
/// <summary>
    ///
This is our custom type descriptor. It creates a new property and returns it along
   
/// with the original list
   
/// </summary>
   
internal sealed class UselessCustomTypeDescriptor : CustomTypeDescriptor
   
{
       
/// <summary>
        ///
Constructor
       
/// </summary>
       
internal UselessCustomTypeDescriptor(ICustomTypeDescriptor parent)
            :
base(parent)
        {
        }

       
/// <summary>
        /// This method add a new property to the original collection
        /// </summary>
        public override PropertyDescriptorCollection GetProperties()
        {
           
// Enumerate the original set of properties and create our new set with it
   
        PropertyDescriptorCollection originalProperties = base.GetProperties();
           
List<PropertyDescriptor> newProperties = new List<PropertyDescriptor>();
           
foreach (PropertyDescriptor pd in originalProperties) newProperties.Add(pd);

           
// Create a new property and add it to the collection
   
        PropertyDescriptor newProperty = TypeDescriptor.CreateProperty(typeof(object), "UselessProperty", typeof(string));
            newProperties
.Add(newProperty);

           
// Finally return the list
   
        return new PropertyDescriptorCollection(newProperties.ToArray(), true);
        }
    }
}

There is obviously the inconsistency I mentioned above with this example. Ideally, we should implement a System.Type and the custom type descriptor’s GetProperties method should be using our System.Type implementation. But hey! As an exercise, why don’t you come up with the implementation?

Let me know if something isn’t clear from above. I will be glad to explain further...

See you soon in my next post on VSD designers...