Understanding the TypeDescriptor: A Metadata Engine for Designtime Code

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:

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?

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?

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.

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.

First stage: The CTD is obtained (as above)
from the latest provider and it returns the initial set of properties.

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.

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.

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;

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

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

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

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

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...