.NET Matters

ICustomTypeDescriptor, Part 2

Stephen Toub

Code download available at:NETMatters0505.exe(163 KB)

Contents

TypeDescriptionProvider
Caching
CustomTypeDescriptor
Multithreading and Memory Models and Vance Morrison
Registering and Using Providers
Data Binding

In last month's .NET Matters column, I answered a question concerning the PropertyGrid control, specifically about using it with classes that expose fields instead of properties. I demonstrated how the ICustomTypeDescriptor interface in the Microsoft® .NET Framework 1.x can be used to craft at run time your own pseudo-properties that wrap each of an object's fields, thus allowing those fields to be displayed and edited in the PropertyGrid. I also showed how this feature can be used with ASP.NET data binding to allow controls to bind to fields on objects.

This month, I'll continue that exploration, delving into changes made for the .NET Framework 2.0 that make my solution much more robust and even easier to implement. If you haven't read last month's column, I suggest you do so before continuing here (see .NET Matters: ICustomTypeDescriptor, Part 1).

TypeDescriptionProvider

System.ComponentModel.ICustomTypeDescriptor is still used and supported in the .NET Framework 2.0. In fact, the .NET Framework 2.0 expands support for it by introducing the new System.ComponentModel.TypeDescriptionProvider feature. This allows you to write a separate class that implements ICustomTypeDescriptor and then to register this class as the provider of descriptions for other types (remember that in the .NET Framework 1.x, the type being described has to implement ICustomTypeDescriptor, or a proxy class that implements it has to be used).

In other words, you can make some minor adjustments to the FieldsToPropertiesTypeDescriptor class shown in last month's .NET Matters column, create a custom provider that derives from TypeDescriptionProvider (and that associates the descriptor class with your settings type), and the PropertyGrid will then use these custom properties without requiring any changes to your type. None. Zero. Pretty cool, huh?

To take advantage of this new functionality, you first need to create a TypeDescriptionProvider. That part's easy, and an example provider for this situation is shown in Figure 1. This custom provider simply derives from TypeDescriptionProvider and overrides its GetTypeDescriptor method. GetTypeDescriptor returns the ICustomTypeDescriptor implementation that TypeDescriptor should use when querying for property descriptors, so this implementation returns an instance of my FieldsToPropertiesTypeDescriptor class.

Figure 1 Implementing a TypeDescriptionProvider

public class FieldsToPropertiesTypeDescriptionProvider : TypeDescriptionProvider { private TypeDescriptionProvider _baseProvider; private PropertyDescriptorCollection _propCache; private FilterCache _filterCache; public FieldsToPropertiesTypeDescriptionProvider(Type t) { _baseProvider = TypeDescriptor.GetProvider(t); } public override ICustomTypeDescriptor GetTypeDescriptor (Type objectType, object instance) { return new FieldsToPropertiesTypeDescriptor( this, _baseProvider.GetTypeDescriptor( objectType, instance), objectType); } ... }

GetTypeDescriptor will be called each and every time that a request is made to TypeDescriptor for information on your type, so it's important that the implementation of ICustomTypeDescriptor that's returned be "flyweight." For example, it should not have a finalizer, and it should have a minimal number of member variables in order to minimize its memory footprint. The classes in the .NET Framework 2.0 that implement this pattern have been profiled extensively, and when it's implemented correctly, this pattern is relatively inexpensive.

You might think it odd that the constructor of this provider calls to the TypeDescriptor.GetProvider method. However, this is a recommended approach when creating providers that are only intended to augment or modify the existing metadata for a type, rather than completely replace it. Unlike in the .NET Framework 1.x, you shouldn't call to the corresponding member of TypeDescriptor from within a method on the ICustomTypeDescriptor returned by a TypeDescriptionProvider.

In the .NET Framework 2.0, TypeDescriptionProviders are the main way that TypeDescriptor gets at metadata, so calling into TypeDescriptor's methods from your provider will almost certainly result in one of those pesky StackOverflowExceptions, for reasons similar to those discussed last month. As such, if you need to create a provider that merely augments metadata rather than creating it from scratch, you'll also need a way to delegate to the previously registered provider for that type. (And if no provider was previously registered, you need access to the default built-in implementation that uses the type's metadata as supplied by the System.Reflection namespace.) So in its constructor, your provider should call to TypeDescriptor.GetProvider to get the current provider for the specified type (if no provider has been added for that type, an internal System.ComponentModel.ReflectTypeDescriptionProvider will be constructed and used). This provider can then be used whenever you need to access the underlying description of the type.

Caching

Note, too, that for this new implementation for the .NET Framework 2.0, the property and attribute caches have been moved from the ICustomTypeDescriptor implementation (where they were in my version targeting the .NET Framework 1.x) into the provider class. In the 1.x version, each instance had its own cache, as the type itself implemented ICustomTypeDescriptor. Since, in this new provider version, GetTypeDescriptor returns a new ICustomTypeDescriptor for each request, caching in the descriptor would be pointless, since the cache would be lost for subsequent requests. Thus, the caching is done at the provider level, with all descriptor instances sharing the provider's cache. To facilitate this, a reference to the provider is also passed to the descriptor in its constructor.

As all of the providers now share access to the same cache, synchronization between multiple threads using TypeDescriptor becomes an issue (it would be a problem in the 1.x version only if the same instance were being described from multiple threads). However, since locking is relatively expensive, the approach I've taken (and that taken by the built-in implementations in the Framework) is to rely on the strong memory model of the common language runtime (CLR) and the fact that reference assignments in the CLR are atomic actions (for more information on memory models, see the "Multithreading and Memory Models" sidebar).

At the beginning of my GetProperties implementation, the method simply copies a reference to the existing cached property sets from the provider. These cached sets are immutable, so when a new set of properties is needed, a new set is generated, and the provider's reference to the cache is simply replaced to point to the new cache. This means that I don't need to take out a lock; however, it means that multiple threads may find it necessary to run the property set generation logic at the same time. If that happens, no errors result, but unnecessary CPU cycles may be consumed as multiple threads duplicate the same work. I don't envision this happening frequently at all (neither does the .NET Framework team), thus making it less expensive to do it this way than to use synchronization primitives around the entire contents of the GetProperties method. However, if you find that this doesn't hold true for your app, you can reexamine this decision and implementation.

CustomTypeDescriptor

TypeDescriptionProvider.GetTypeDescriptor returns the ICustomTypeDescriptor implementation that provides the metadata to the client, and so it must be passed the type of the object being described, the actual instance of the object being described (if one was specified), and the base provider to which it can delegate, as discussed earlier.

Multithreading and Memory Models and Vance Morrison

There is little wonder that confusion exists about correctly writing multithreaded programs. Multithreaded programs are inherently nondeterministic. In addition, because threads interact through reads and writes to memory, the behavior of a multithreaded program is much more dependent than a sequential program on exactly when reads and writes happen. Finally, hardware and compilers continually evolve, making it very difficult to specify concisely the optimizations they perform today and those they will do going forward.

One key to cutting through this confusion is a specification between the programmer and the compiler and hardware on exactly what it means to read and write memory. This specification is called the memory model. At first glance, it would seem that that this specification should be straightforward: reads and writes from each thread should happen in program order, and reads and writes from different threads can be interleaved in any order. This memory model is what most programmers have in mind when they write multithreaded programs and has a formal name: sequential consistency.

Unfortunately, sequential consistency is too restrictive for compiler writers and hardware manufacturers. This model disallows modifying any read or write that came from the same thread. This means a compiler optimization as simple as caching a memory fetch in a register and reusing it more than once is now illegal (since now there are fewer memory reads). It also disallows most forms of caching, write buffering, and other hardware techniques to speed memory access. Clearly there is a need to relax sequential consistency to allow for these optimizations.

One such relaxed model is specified in section 12, Partition I of the .NET runtime ECMA standard (see msdn.microsoft.com/vstudio/aa569283.aspx). In this model, compilers and memory hardware are allowed to reorder ordinary reads and writes even from a single thread as long as the following rules are obeyed:

  1. The behavior of a thread run in isolation isn't changed.
  2. Reads and writes cannot move before a volatile read or the entering of a lock.
  3. Reads and writes cannot move after a volatile write or the leaving of a lock.

This model basically allows compilers and hardware to optimize most ordinary reads exactly as they would have if the program were not multithreaded. This model works very well if programmers always follow a simple (but hard to follow) locking protocol: a lock is associated with every memory location that can be accessed from more than one thread. This lock is entered before any access to that location and must be released before another thread can access the location.

This model is elegant. Even in the sequential consistency model, programs generally need to follow the locking protocol to ensure that other threads only see complete updates to any particular data structure. The relaxed model exploits this by requiring the compilers and memory hardware to respect only the boundaries created by these locks, but no more.

This relaxed model works perfectly if programs follow the locking protocol mentioned earlier. However, there are times when there is a desire to avoid this protocol. Entering locks can be expensive (at least dozens of cycles, and possibly significantly more) and for frequently accessed locks, this overhead can be significant. Unfortunately, once you remove the locks, not only can other threads read or write data structures while they are being updated, but the compiler and memory system can reorder your reads and writes!

As an example of just how subtle things are, consider one of the few places where you can write correct, lock-free code: lazy initialization. In this code, a variable starts out uninitialized, and the first thread that tries to fetch it will initialize it.

private static LazyInitClass myValue = null; public static LazyInitClass GetValue() { if (myValue == null) myValue = new LazyInitClass(); return myValue; }

Since this code does not follow the locking protocol (there are no locks), it is dangerous code. Even in the strong sequential consistency model, this code has issues. Two threads can be simultaneously running the "if" statement, both find it null, and both initialize myValue. If this were C++ code, this would cause a memory leak. Even in a garbage-collected environment, there is a problem if it is important that myValue have a unique identity (you use pointer equality).

In a relaxed memory model there are even more subtle problems. The assignment to myValue might be moved before the writes that are in LazyInitClass's constructor. This means other threads can access the fields of the object myValue references before it is completely initialized! Now you may be thinking this won't happen on "real" computers, and you would be partially right. On a uniprocessor, or even a multiprocessor x86 computer, it turns out that this memory reordering can't happen. However, on a multiprocessor IA64 computer, it definitely can (we have the stress failures to prove it).

The write reordering issue can be fixed by a trivial code change. By adding "volatile" to the declaration of myValue, you force the system to keep the writes in the proper order. It turns out that even this change is not strictly needed on the .NET runtime because Microsoft has decided on a memory model that guarantees writes are never reordered (this means the CLR has a stronger memory model than the ECMA specification provides).

Note that the previous example is one of the simplest low-lock code patterns, and it is already filled with subtleties. All of these subtleties are avoided simply by following the locking protocol, so that should always be the design of choice. When you are forced by performance considerations to use low-lock code patterns, you need a more thorough introduction to the issues than is given here (the CLR team will make the exact rules available before the .NET Framework 2.0 ships). It is best to stick closely to patterns that have been carefully reviewed by people with a deep knowledge of multithreading issues.

Figure 2 Using TypeDescriptionProvider in the .NET Framework 2.0

Figure 2** Using TypeDescriptionProvider in the .NET Framework 2.0 **

The new implementation of FieldsToPropertiesTypeDescriptor I've written for the .NET Framework 2.0 implements the process depicted in Figure 2 and is shown in Figure 3. It should look very familiar. Just like with the 1.x version, this code delegates most of its functionality. However in this case, it's delegating to another descriptor instance rather than to TypeDescriptor. Additionally, decisions are made based on the specified instance rather than on itself, since the type being described no longer derives from this descriptor type (in my 1.x version, this descriptor class was the base type for the type being described). Also, this class no longer directly implements ICustomTypeDescriptor, but instead derives from the new abstract class System.ComponentModel.CustomTypeDescriptor. This class implements all of the boilerplate code so I don't have to.

Figure 3 FieldsToPropertiesTypeDescriptor for the .NET Framework 2.0

private class FieldsToPropertiesTypeDescriptor : CustomTypeDescriptor { private Type _objectType; private FieldsToPropertiesTypeDescriptionProvider _provider; public FieldsToPropertiesTypeDescriptor( FieldsToPropertiesTypeDescriptionProvider provider, ICustomTypeDescriptor descriptor, Type objectType) : base(descriptor) { if (provider == null) throw new ArgumentNullException("provider"); if (descriptor == null) throw new ArgumentNullException("descriptor"); if (objectType == null) throw new ArgumentNullException("objectType"); _objectType = objectType; _provider = provider; } public override PropertyDescriptorCollection GetProperties() { return GetProperties(null); } public override PropertyDescriptorCollection GetProperties( Attribute[] attributes) { // Retrieve cached properties and filtered properties bool filtering = attributes != null && attributes.Length > 0; FilterCache cache = _provider._filterCache; PropertyDescriptorCollection props = _provider._propCache; // Use a cached version if we can if (filtering && cache != null && cache.IsValid(attributes)) return cache.FilteredProperties; else if (!filtering && props != null) return props; // Otherwise, create the property collection props = new PropertyDescriptorCollection(null); foreach (PropertyDescriptor prop in base.GetProperties(attributes)) props.Add(prop); foreach (FieldInfo field in _objectType.GetFields()) { FieldPropertyDescriptor fieldDesc = new FieldPropertyDescriptor(field); if (!filtering || fieldDesc.Attributes.Contains(attributes)) props.Add(fieldDesc); } // Store the updated properties if (filtering) { cache = new FilterCache(); cache.FilteredProperties = props; cache.Attributes = attributes; _provider._filterCache = cache; } else _provider._propCache = props; // Return the computed properties return props; } }

You'll notice that FieldsToPropertiesTypeDescriptor is private. I've actually declared it as a nested class within FieldsToPropertiesTypeDescriptionProvider (private accessibility is only valid on types when they're nested), and I've done this for three reasons. First, there's no need for anyone external to access a strongly typed instance of FieldsToPropertiesTypeDescriptor; all relevant functionality is available through its ICustomTypeDescriptor interface. Second, I wanted to make the property caches inside of the provider private, and a class external to the provider does not have access to a private member, whereas a nested class does. Third, it keeps the entire implementation of this provider in one place and makes it very easy to modify these internal implementation details without requiring external dependencies to change, should a modification be necessary in the future.

Registering and Using Providers

With that in place, forcing the PropertyGrid to show a type's fields as properties (and allowing ASP.NET data binding to work with fields) can now be done without any modifications to the type in question and with only one code statement:

TypeDescriptor.AddProvider(new FieldsToPropertiesTypeDescriptionProvider (typeof(Person)), typeof(Person));

This snippet registers an instance of my provider for the Person class. From this point on (or until the provider is removed with the TypeDescriptor.RemoveProvider method), any requests for a type description of an instance of Person or for the type Person will go through my custom provider.

Additionally, a new attribute to the .NET Framework 2.0, System.ComponentModel.TypeDescriptionProviderAttribute, allows you to associate a provider with a particular type (by attributing that type) in metadata, thus completely eliminating the need for manual registration. Using that attribute, the declaration of the type in question would look something like the following:

[TypeDescriptionProvider( typeof(FieldsToPropertiesTypeDescriptionProvider))] public class Person { public string Name; public int Age; public string Hobbies; }

Unfortunately, TypeDescriptor expects that providers used with the TypeDescriptionProviderAttribute have a parameterless public constructor. My FieldsToPropertiesTypeDescriptionProvider was engineered to be totally generic in that it could be used with any type. To get it to work with TypeDescriptionProviderAttribute, the simplest solution is to hardcode the associated type into the provider's constructor and to then change the constructor to be parameterless. As an example, instead of the current constructor which is coded as follows:

public FieldsToPropertiesTypeDescriptionProvider(Type t) { _baseProvider = TypeDescriptor.GetProvider(t); }

I could specify here that this provider will be used only with the Person type, as shown in the following code:

public FieldsToPropertiesTypeDescriptionProvider() { _baseProvider = TypeDescriptor.GetProvider(typeof(Person)); }

While TypeDescriptionProviderAttribute can be very handy in certain situations, it's not intended to be used with every kind of provider, and it's probably not appropriate for this situation.

I mentioned last month briefly that in the .NET Framework 2.0 Beta 1 (also true for Beta 2), certain invocations of TypeDescriptor methods will filter for you, and certain invocations won't. It actually depends on the overload of TypeDescriptor.GetProperties that you use. If you pass an instance to TypeDescriptor.GetProperties, then filtering will be performed for you automatically, just as with the .NET Framework 1.x.

If, however, you pass a Type to TypeDescriptor.GetProperties, it won't filter for you automatically. The base set of properties you retrieve for your type from within your ICustomTypeDescriptor (relying on the base provider) will be filtered by the System.ComponentModel.ReflectTypeDescriptionProvider used internally, but any properties you then add (such as the property for each field) will not automatically be filtered after the set is returned from your descriptor. Thus, you must do it manually, and hence why I've chosen to do it in all cases. It's just simpler to do so, and the performance impact should be minimal in most scenarios. Note, however, that this behavior will almost certainly change by the final release of the .NET Framework 2.0 so that all paths will be filtered by the Framework. Implementing filtering in your type descriptors will then be optional and should only be done if there is a good performance reason for doing so (for example, if the expense of the extra filtering step is less than the expense of otherwise creating the additional descriptors, both in running time and in development time).

Data Binding

This type of solution is very useful, and for more scenarios than just the one described here. A handful of Framework classes, including DataRowView, implement ICustomTypeDescriptor in order to provide a custom description of the type. Moreover, PropertyGrid isn't the only class in the Framework that takes advantage of TypeDescriptor. DetailsView, BindingList<T>, and ExpandableObjectConverter, to name just a few, all take advantage of TypeDescriptor and will thus make use of any TypeDescriptionProviders you implement.

But more importantly, I discussed last month how ASP.NET data binding also relies on TypeDescriptor to retrieve property information about an object. In the .NET Framework 1.x, the proxy approach I described (creating a proxy class that implements ICustomTypeDescriptor, wraps your object, and returns information about your object) is often the best solution when working with the PropertyGrid, as it allows you to keep your original classes intact, without modification.

However, often ASP.NET data binding is used with a collection or an array of objects, and each of these would need to be wrapped in a proxy for this approach to be successful with data binding. Type description providers solve that problem. Before binding your collection to a DataGrid, a DataList, or a ListControl (such as ListBox or DropDownList), all of which make use of the Databinder class, which in turn uses TypeDescriptor, simply register a TypeDescriptionProvider for your object's type. That provider (and the type descriptor it supplies) will then be used by the list or grid when determining what data needs to be rendered for an object of that type. Makes you feel powerful, doesn't it?

Send your questions and comments to  netqa@microsoft.com.

Stephen Toub is the Technical Editor for MSDN Magazine.