Click here to Skip to main content
Click here to Skip to main content

HyperDescriptor: Accelerated dynamic property access

By , 20 Apr 2007
 

Introduction

.NET provides flexible data-binding and runtime property access, but by default this is via reflection an is known to be relatively slow. This article uses the power of Reflection.Emit to provide a pre-compiled (and much accelerated) implementation for reflection properties, and demonstrates the use of TypeDescriptionProvider to dynamically apply this implementation to types.

A lot of technical details are included, but this code is all provided in the source; as a consumer you have to do almost nothing. Really. You may wish to jump ahead to the usage scenarios, then dip back if you want to know what makes it all work.

Background

Data-binding (and various other runtime access) uses the System.ComponentModel; this is how a type or instance says "I have these properties." Each property is expressed as a PropertyDescriptor, which provides information on the underlying type, the name, and (most importantly) a GetValue() and SetValue() method to allow access to the data. By default the framework uses reflection to provide access to the properties defined against the types - but there is a problem: reflection is (relatively speaking) slow (quantified below). If you use a lot of data binding, or need to dynamically access properties at runtime, then this can be a drain.

Specifically, the following ("SAMPLE 1", "SAMPLE 2") achieve the same thing, but perform very differently:

public class MyEntity {
    private string name;
    public event EventHandler NameChanged;
    public string Name {
        get {return name;}
    }
    set {
        if (value != Name) {
            name = value;
            EventHandler handler = NameChanged;
            if (handler != null) handler(this, EventArgs.Empty);
        }
    }
}
//...

MyEntity someInstance = //TODO
// SAMPLE 1: compiled property access
string name1 = someInstance.Name;

// SAMPLE 2: runtime property access (standard pattern using 
//TypeDescriptor)
string name2 = (string) TypeDescriptor.GetProperties(someInstance)
    ["Name"].GetValue(someInstance);

The latter must make a myriad of calls to verify the arguments (since everything is typed as object), and must do a lot of work to correctly call the property getter at runtime. Our objective is to eliminate as much of this overhead as possible.

Reflection, fortunately, is not the end of the story. In 1.1, the Framework supported the ICustomTypeDescriptor interface; by passing an instance to GetProperties(), the system can query this interface and supplement the properties. This is similar to how a DataRow exposes binding properties that match the columns of the DataTable, instead of the properties on the DataRow class. But again, it is not ideal:

  • It requires the instance to implement the complex ICustomTypeDescriptor interface (a lot of work)
  • It cannot be applied to types outside of your control
  • It does not work when asking about a type rather than an instance

.NET 2.0 takes this further; by using a TypeDescriptionProvider, this allows us to delegate provision of runtime type information to separate classes. More: we can actually supply providers at runtime - meaning we can effective extend / replace the properties available. Neat.

PropertyDescriptor implementation

In order to change the performance, our end-game is to make "SAMPLE 2" run "SAMPLE 1" internally rather than using reflection. For a single known type, this is relatively easy, if boring; we simply need to create a PropertyDescriptor class for each property on the type, and perform the casts as compile-time:

public sealed class MyEntityNamePropertyDescriptor : ChainingPropertyDescriptor
{
public MyEntityNamePropertyDescriptor(PropertyDescriptor parent) : 
    base(parent) {}
public override object GetValue(object component) {
    return (string) ((MyEntity)component).Name;
}
public override void SetValue(object component, object value) {
    ((MyEntity)component).Name = (string)value;
}
public override bool IsReadOnly {
    get { return false; }
}
public override bool SupportsChangeEvents {
    get { return true; }
}
public override void AddValueChanged(object component, EventHandler handler) {
    ((MyEntity)component).NameChanged += handler;
}
public override void RemoveValueChanged(object component, EventHandler handler)
 {
    ((MyEntity)component).NameChanged -= handler;
}        
}

(Here, ChainingPropertyDescriptor is a simple PropertyDescriptor implementation that supports chaining by invoking the parent's implementation for each of the many methods.)

However, this approach is clearly only possible for types and properties we know about in advance, and even then it would be a nightmare to keep it all up to date. That is where Reflection.Emit comes in; this is a mechanism for meta-programming - i.e. we can (at runtime) create a set of classes just like the above. Although other approaches (like CodeDom) are feasible, Reflection.Emit is (to my mind) the cleanest to use. Unfortunately, it requires you to code in IL. I am not an IL expert, so my high-tech approach was to write 4 PropertyDescriptors like above, and look at the generated IL in ILDASM and Reflector:

  • A class entity with a class property
  • A class entity with a struct property
  • A struct entity with a class property
  • A struct entity with a struct property

Fortunately, the IL for these simple methods is quite simple; to illustrate with GetValue() (although a full explanation of IL is not given here):

MethodBuilder mb;
MethodInfo baseMethod;
if (property.CanRead) {
    // obtain the implementation that we want to override
    baseMethod = typeof(ChainingPropertyDescriptor).GetMethod("GetValue");
    // create a new method that accepts an object and returns an object 
    // (as per the base)
    mb = tb.DefineMethod(baseMethod.Name,
        MethodAttributes.HideBySig | MethodAttributes.Public | 
            MethodAttributes.Virtual | MethodAttributes.Final,
        baseMethod.CallingConvention, baseMethod.ReturnType, new Type[] { 
            typeof(object) });
    // start writing IL into the method
    il = mb.GetILGenerator();
    if (property.DeclaringType.IsValueType) {
        // unbox the object argument into our known (instance) struct type
        LocalBuilder lb = il.DeclareLocal(property.DeclaringType);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Unbox_Any, property.DeclaringType);
        il.Emit(OpCodes.Stloc_0);
        il.Emit(OpCodes.Ldloca_S, lb);
    } else {
        // cast the object argument into our known class type
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Castclass, property.DeclaringType);
    }
    // call the "get" method
    il.Emit(OpCodes.Callvirt, property.GetGetMethod());
    if (property.PropertyType.IsValueType) {
        // box it from the known (value) struct type
        il.Emit(OpCodes.Box, property.PropertyType);
    }
    // return the value
    il.Emit(OpCodes.Ret);
    // signal that this method should override the base
    tb.DefineMethodOverride(mb, baseMethod);
}

This is repeated for the other cited overrides (note, however, that we only override SetValue(), AddValueChanged() and RemoveValueChanged() for classes, since for structures the purpose of the change would lost during unboxing; in these cases simply defer to the original reflection implementation).

TypeDescriptionProvider implementation

The job of the TypeDescriptionProvider is to return an ICustomTypeDescriptor that describes the specified type/instance. As we are aiming at reflective classes we can focus on types (not instances), and since we are using dynamic type creation we want to re-use any generated classes. To do this we will simply cache a specific ICustomTypeDescriptors for each type we are asked about. In order to get a starting point we will again use chaining (this is supported on a base ctor overload) - i.e. we could use the previously defined provider for a given type. As a final measure, we will simply use the global provider - i.e. the provider that is defined for "object".

sealed class HyperTypeDescriptionProvider : TypeDescriptionProvider {
public HyperTypeDescriptionProvider() : this(typeof(object)) { }
public HyperTypeDescriptionProvider(Type type) : 
    this(TypeDescriptor.GetProvider(type)) { }
public HyperTypeDescriptionProvider(TypeDescriptionProvider parent) : 
    base(parent) { }
public static void Clear(Type type) {
    lock (descriptors) {
        descriptors.Remove(type);
    }
}
public static void Clear() {
    lock (descriptors) {
        descriptors.Clear();
    }
}
private static readonly Dictionary<Type, ICustomTypeDescriptor> descriptors
= new Dictionary<Type, ICustomTypeDescriptor>();

public sealed override ICustomTypeDescriptor GetTypeDescriptor(Type objectType,
    object instance) {
    ICustomTypeDescriptor descriptor;
    lock (descriptors) {
        if (!descriptors.TryGetValue(objectType, out descriptor)) {
            try {
                descriptor = BuildDescriptor(objectType);
            } catch {
                return base.GetTypeDescriptor(objectType, instance);
            }
        }
        return descriptor;
    }
}
[ReflectionPermission( SecurityAction.Assert, 
    Flags = ReflectionPermissionFlag.AllFlags)]
private ICustomTypeDescriptor BuildDescriptor(Type objectType) {
    // NOTE: "descriptors" already locked here

    // get the parent descriptor and add to the dictionary so that
    // building the new descriptor will use the base rather than recursing
    ICustomTypeDescriptor descriptor = base.GetTypeDescriptor(objectType, 
        null);
    descriptors.Add(objectType, descriptor);
    try {
        // build a new descriptor from this, and replace the lookup
        descriptor = new HyperTypeDescriptor(descriptor);
        descriptors[objectType] = descriptor;
        return descriptor;
    } catch {
        // rollback and throw
        // (perhaps because the specific caller lacked permissions;
        // another caller may be successful)
        descriptors.Remove(objectType);
        throw;
    }
}

ICustomTypeDescriptor implementation

Again, chaining to the rescue. We don't need to implement everything - just the bits we want to change. Specifically, we'll ask the base descriptor about the properties *it* knows about, and then we'll inspect them for something that smells like reflection:

sealed class HyperTypeDescriptor : CustomTypeDescriptor {
    private readonly PropertyDescriptorCollection properties;
    internal HyperTypeDescriptor(ICustomTypeDescriptor parent) : 
        base(parent) {
        properties = WrapProperties(parent.GetProperties());
    }
    public sealed override PropertyDescriptorCollection GetProperties(
        Attribute[] attributes) {
        return properties;
    }
    public sealed override PropertyDescriptorCollection GetProperties() {
        return properties;
    }
    private static PropertyDescriptorCollection WrapProperties(
        PropertyDescriptorCollection oldProps) {
        PropertyDescriptor[] newProps = new PropertyDescriptor[oldProps.Count];
        int index = 0;
        bool changed = false;
        // HACK: how to identify reflection, given that the class is internal
        Type wrapMe = Assembly.GetAssembly(typeof(PropertyDescriptor)).
            GetType("System.ComponentModel.ReflectPropertyDescriptor");
        foreach (PropertyDescriptor oldProp in oldProps) {
            PropertyDescriptor pd = oldProp;
            // if it looks like reflection, try to create a bespoke descriptor
            if (ReferenceEquals(wrapMe, pd.GetType()) && 
                TryCreatePropertyDescriptor(ref pd)) {
                changed = true;
            }
            newProps[index++] = pd;
        }

        return changed ? new PropertyDescriptorCollection(newProps, true) : 
            oldProps;
    }
    // TryCreatePropertyDescriptor not shown, but flavor indicated previously
}

Using the code

At this point it gets much easier. There are two primary ways of hooking our new provider into the object model; first, via TypeDescriptionProviderAttribute:

[TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
public class MyEntity {
    // ...
}

Obviously this is only suited to types under our control, but is very expressive. It is not necessary to include the attribute on sub-types since TypeDescriptor automatically looks at ancestors to resolve a provider.

The second approach is via TypeDescriptor.AddProvider(). Again this supports inheritance, but to make our lives easier (with chaining, etc.) we can expose a helper:

sealed class HyperTypeDescriptionProvider : TypeDescriptionProvider {
    public static void Add(Type type) {
        TypeDescriptionProvider parent = TypeDescriptor.GetProvider(type);
        TypeDescriptor.AddProvider(new HyperTypeDescriptionProvider(parent), 
            type);
    }
    // ...
}
// ...
HyperTypeDescriptionProvider.Add(typeof(MyEntity));

Performance

So how does it perform? How about we use runtime property access a great many times (to make even the crudest timers reasonable) and see how it behaves with and without the change (see sample). In particular, we will measure the time to execute:

  • For direct (hard-coded) access (against a single property for brevity):
    • property get
    • property set
    • event add/remove
    • Op count (as a check that everything happened)
  • For indirect (System.ComponentModel) access (against several properties, including inheritance):
    • GetProperties
    • IsReadOnly
    • SupportsChangeEvents
    • GetValue
    • SetValue (if supported)
    • AddHandler/RemoveHandler (if supported)
    • Op count (as a check that everything happened)

Here's the results from a release build:

Direct access
    MyEntity.Name    GetValue    8ms
    MyEntity.Name    SetValue    97ms
    MyEntity.Name    ValueChanged    1022ms
  OpCount: 25000000

Without HyperTypeDescriptionProvider
    MyEntity.Name    GetProperties    647ms
    MyEntity.Name    IsReadOnly    2926ms
    MyEntity.Name    SupportsChangeEvents    245ms
    MyEntity.Name    GetValue    10360ms
    MyEntity.Name    SetValue    20288ms
    MyEntity.Name    ValueChanged    29566ms
  OpCount: 25000000
    MySuperEntity.Name    GetProperties    828ms
    MySuperEntity.Name    IsReadOnly    2881ms
    MySuperEntity.Name    SupportsChangeEvents    241ms
    MySuperEntity.Name    GetValue    10682ms
    MySuperEntity.Name    SetValue    20730ms
    MySuperEntity.Name    ValueChanged    30979ms
  OpCount: 25000000
    MySuperEntity.When    GetProperties    825ms
    MySuperEntity.When    IsReadOnly    2888ms
    MySuperEntity.When    SupportsChangeEvents    251ms
    MySuperEntity.When    GetValue    11393ms
    MySuperEntity.When    SetValue    22416ms
  OpCount: 10000000

With HyperTypeDescriptionProvider
    MyEntity.Name    GetProperties    699ms
    MyEntity.Name    IsReadOnly    43ms
    MyEntity.Name    SupportsChangeEvents    41ms
    MyEntity.Name    GetValue    57ms
    MyEntity.Name    SetValue    155ms
    MyEntity.Name    ValueChanged    954ms
  OpCount: 25000000
    MySuperEntity.Name    GetProperties    914ms
    MySuperEntity.Name    IsReadOnly    41ms
    MySuperEntity.Name    SupportsChangeEvents    44ms
    MySuperEntity.Name    GetValue    95ms
    MySuperEntity.Name    SetValue    173ms
    MySuperEntity.Name    ValueChanged    1059ms
  OpCount: 25000000
    MySuperEntity.When    GetProperties    891ms
    MySuperEntity.When    IsReadOnly    41ms
    MySuperEntity.When    SupportsChangeEvents    46ms
    MySuperEntity.When    GetValue    295ms
    MySuperEntity.When    SetValue    110ms
  OpCount: 10000000

First - note the OpCounts all match up before and after, so we are achieving the same amount of useful work. GetValue on Name, for instance has gone from >10s to <100ms - in fact about 180 times faster. For When there is an additional fixed cost from boxing, but still a 40-fold improvement. More important, however, is the comparison to direct (compiled) access; we are nearly there! Looking at the figures as a factor or direct compiled access gives ~7, ~1.5and ~1 for "get", "set" and "event" access, which is very satisfactory (the latter being due to the fixed overhead of Delegate manipulations). Metadata (GetProperties) access is a curiosity; on some machines it is faster, on some slower, so we'll call it even. Still, a vast improvement.

The real joy is you can apply this as sparingly or as liberally as you like; either apply it to that important class that you bind to in a grid, or apply to System.Windows.Forms.Control and see what happens (I haven't tried this; one wonders if it might improve the performance of Visual Studio, particularly the designer which depends heavily on PropertyGrid and the System.ComponentModel). I would definitely stop well short of applying it to object though.

Summary

  • Much faster runtime reflective data access
  • Applicable to both existing (external) classes and new builds
  • Provides a framework for additional functionality

History

  • 20 Apr 2007: Improved metrics; corrected class name(!); tweaked to take account of ReflectionPermission (Assert and fail-safe) - thanks to Josh Smith
  • 15 Apr 2007: Support change notifications. Improved sample output. Use parent's answer to SupportsChangeEvents and IsReadOnly. Revised namespace and class names
  • 13 Apr 2007: First version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Marc Gravell
Software Developer Stack Exchange
United Kingdom United Kingdom
Member
Geek at Stack Exchange

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionConfused on how to implementmemberCTI Guru24 May '13 - 6:07 
Hi, I've read very good things about this on the intertubes, but I'm having trouble groking how to use this. I've converted HyperDescriptor to .NET 4 via a comment on StackOverflow.
 
I use the following method to assign dictionary values to an objects properties where the dictionary keys match properties in the DTO.
 
It seems that this would be simple and ripe for HyperDescriptor, but I'm having trouble implementing.
 
(I'm leaving out some implementation not relevant for discussion because it would confuse the matter, let's just say I need Reflection to accomplish what I need. Hence, HD.)
 
private void BuildClassDTOFromDictionary( Dictionary userData )
{
    ClassDTO classDTO = new ClassDTO();
    //  Get type.
    Type type = typeof( ClassDTO );
    //  Get Properties.
    System.Reflection.PropertyInfo[] properties = type.GetProperties();
    //  Iterate through all of the KVPs.
    foreach( string key in userData.Keys )
    {
        //  Use this key to get the value from UserData.
        string value = userData.Get( key );
        //  Loop over properties.
        foreach( System.Reflection.PropertyInfo propertyInfo in properties )
        {
            //  Get name.
            string name = propertyInfo.Name;
            //  Is this key matching the property name?
            if( name.Equals( key, StringComparison.OrdinalIgnoreCase ) == true )
            {
                //  Assign the Property the value
                try
                {
                    //  Could throw several Reflection exceptions...
                    propertyInfo.SetValue( classDTO, value, null );
                }
                catch( Exception )
                {
                    //  ...just eat them
                }
                break;
            }
        }
    }
    //... do something wonderful with classDTO
}
 
Thanks much.
QuestionGetProperties is not workingmemberChitttapa11 Sep '12 - 5:49 
Hi Mark,
Used your code as VB.NET but it looks like the GetProperties is not working.
 
Dim props As PropertyDescriptorCollection = Nothing
props = TypeDescriptor.GetProperties(A)
 
This statement returns NULL.
 
I also want to know if Code can be integrated with the Dynimic Demo (SilverLight) example.
 

Here is the snippet of the code in which I want to integrate above code so that below code works faster.
 
Namespace DynamicSilverLightVB
Public Class DynamicDemo
#Region "var's"
 
Private extendedType As Type = Nothing
 
#End Region
 
#Region "CreatePresentable"
 
Public Function CreatePresentable(ByVal src As Object, ByVal extendedPropertiesDict As Dictionary(Of String, Type)) As PresentingData
'create the Object type & class based on the dictionary - hold the extended class in a variable of the base-type
Dim classicEx As PresentingData = DynamicFactory.CreateClass(Of PresentingData)(GetExtendedType(Of PresentingData)(extendedPropertiesDict))
'fill in the base-type's properties
classicEx.Composer = src.ComposerName
classicEx.Composition = src.CompositionName
classicEx.Orquestra = src.Orquestra
classicEx.Conductor = src.Conductor
'fill in the dynamically created properties
SetExtendedProperties(TryCast(classicEx, Object), src, extendedPropertiesDict)
Return classicEx
End Function
 
#End Region
 
#Region "GetExtendedType"
 
'generic method that will extend dynamically the type T. keeping a global variable that holds the Object type
'makes sure I create type only once (the class on the other hand has to be instantiated for each data-row
Protected Function GetExtendedType(Of T As Class)(ByVal extendedPropertiesDict As Dictionary(Of String, Type)) As Type
If extendedType Is Nothing Then
extendedType = DynamicFactory.ExtendTheType(Of T)(extendedPropertiesDict)
End If
Return extendedType
End Function
 
#End Region
 
#Region "SetExtendedProperties"
 
'generic method that enumerates the dictionary and populate the Object class with values from the source
'class that contains all the 20 columns.
'there is an assumption here that the newly created properties have the same name as the original ones
'in the source class.
'the Object class (destination) is passed-in using the keyword Object in order defer the GetType()
'operation until runtime when the Object type is available.
Public Sub SetExtendedProperties(Of T)(ByVal dest As Object, ByVal src As T, ByVal extendedPropsDict As Dictionary(Of String, Type))
For Each word As Object In extendedPropsDict
Dim src_pi = src.[GetType]().GetProperty(word.Key)
Dim dest_pi = TryCast(dest.[GetType]().GetProperty(word.Key), PropertyInfo)
Dim val = src_pi.GetValue(src, Nothing)
'format the data based on its type
If TypeOf val Is DateTime Then
dest_pi.SetValue(dest, DirectCast(val, DateTime).ToShortDateString(), Nothing)
ElseIf TypeOf val Is Decimal Then
dest_pi.SetValue(dest, CDec(val).ToString("C"), Nothing)
Else
dest_pi.SetValue(dest, val, Nothing)
End If
Next
End Sub
 
#End Region
End Class
QuestionNot working with virtual properties :(memberdgi25 Nov '11 - 13:31 
Great tool but...
 
Not working with virtual properties Frown | :(
 
Best regards
SuggestionGreat toolmemberMember 823101612 Sep '11 - 5:42 
One tiny improvement would be to check the properties dictionary right after the call to descriptor.SupportsChangeEvents.
 
If you only mark up the base class, on the first inherited property in the child the SupportsChangeEvents call loops in to build the properties of the base class and this has the consequence of filling in the properties dictionary, so by the time we come back to the child, the property we're building has already been added and properties.Add(property, descriptor) ends up throwing an exception, which (I believe) subsequently prevents this one property from using the hyper descriptor in the child.
 
Something like the below would avoid this scenario.
 
bool supportsChangeEvents = descriptor.SupportsChangeEvents, isReadOnly = descriptor.IsReadOnly;
 
//descriptor.SupportsChangeEvents calls to base classes which might prepopulate the property into the dictionary
if (properties.TryGetValue(property, out foundBuiltAlready))
{
    descriptor = foundBuiltAlready;
    return true;
}

GeneralMy vote of 5membervkuzmich15 Oct '10 - 4:05 
Very nice
QuestionTypeDescriptor.GetFullComponentName(object component) returns nullmemberalexanderdev12 Mar '10 - 0:11 
can you please advise how to correctly call default implementation?
AnswerRe: TypeDescriptor.GetFullComponentName(object component) returns nullmemberalexanderdev12 Mar '10 - 1:05 
actually the default implementation returns null too. (string Name = TypeDescriptor.GetFullComponentName(new Collection<string>());
 
so all I need is just to use TypeDescriptor.GetClassName(Component) instead. It returns fully qualified class name.
QuestionStackoverflow ExceptionmemberMcGiv13 Jan '10 - 0:41 
Hi,
 
I'm getting a stackoverflow exception when I add two types where one type inherits from another. See the nunit test below. I'm using .Net 3.5, am I using the code incorrectly as I'm sure a bug like this would have been picked up by now?
 
Damien
 

public class Entity
{
	public string Name { get; set; }
}
 
public class EntityEx :Entity
{
	public string FullName { get; set; }
}  
 
[TestFixture]
public class HyperPropertyDescriptorTests
{
	[Test]
	public void Stackoverflow()
	{
		HyperTypeDescriptionProvider.Add(typeof(EntityEx));
		HyperTypeDescriptionProvider.Add(typeof(Entity));
		
		var Properties = TypeDescriptor.GetProperties(typeof(EntityEx));
		Properties = TypeDescriptor.GetProperties(typeof(Entity)); // causes stack overflow
	}
}

AnswerRe: Stackoverflow ExceptionmemberMarc Gravell13 Jan '10 - 1:22 
OK; I'll have a look - I've got some other changes to do too.
AnswerRe: Stackoverflow ExceptionmemberMarc Gravell14 Jan '10 - 21:50 
I'm still investigating the stack overflow, but note that a simple fix is... don't do that Poke tongue | ;-P It works fine (including sub-types) if you set at the base only:
 
HyperTypeDescriptionProvider.Add(typeof(Entity));

GeneralRe: Stackoverflow ExceptionmemberMcGiv14 Jan '10 - 23:37 
Unfortunately not possible in my case as I lazy load and cache PropertyInfo objects of a type when needed so the order that they would be added to HyperDescriptor is unknown and can change depending on usage.
 
The unit test was only an example to prove the exception.
 
I ended up using an Expression.Lambda instead to get a bit of performance gain, would still like to test HyperPropertyDescriptor though.
 
Damien
GeneralRe: Stackoverflow Exceptionmemberavibhor9 Jan '12 - 8:45 
I am getting same error. Any solution for this???
GeneralRe: Stackoverflow Exceptionmembermattstermiller17 Jul '12 - 10:47 
I just posted a fix, see below Smile | :)
AnswerRe: Stackoverflow Exception [modified]membermattstermiller17 Jul '12 - 10:46 
I also had this problem, and needed to find a solution since I was using it in a generic library where I couldn't make any assumptions about the structure or relations of the types I was adding to the Hyper providers.
 
After grappling with this problem for a couple days with a massive headache, I finally found a solution to this. I added the following code in GetTypeDescriptor() just before SyncLock descriptors:
 
' prevent infinite recursion when using both a type and a subclass of that type
If descriptors.ContainsKey(objectType.BaseType) Then
    Return MyBase.GetTypeDescriptor(objectType, instance)
End If
 
I'm still not 100% sure what impact this has, and it may mean that sub-types don't see the performance benefits, but that is far better than not working at all. Big Grin | :-D
 
Edit: See reply for better solution!

modified 5 Mar '13 - 18:19.

GeneralRe: Stackoverflow Exception [modified]membermattstermiller5 Mar '13 - 12:16 
I created a series of tests that tries all the different combinations and orders of adding a base type and sub-type to the HyperTypeDescriptionProvider and getting the PropertyDescriptors for those types, to nail down the exact circumstances that will cause the stack overflow. I found two situations that resulted in the error:
- Add base type to hyper
- Get base type descriptors
- Add sub-type to hyper
- Get sub type descriptors <-- causes stack overflow
And:
- Add sub-type to hyper
- Add base type to hyper
- Get sub type descriptors <-- causes stack overflow
 
I also created a series of tests that measures the time to access properties before and after adding/removing types from Hyper, also using different combinations and orders of a base and sub-type, to ensure that no matter what order you add types in, you will see the performance benefit.
 
Another problem that I found was that removing or clearing a type from HyperTypeDescriptionProvider did not actually remove the type from TypeDescriptor. Not only that, but it could cause the stack overflow if you removed a type and re-added it.
 
I was determined to fix the stack overflow, the type removal, and get all of my tests to pass. I am proud to announce that I was successful!! I cannot get it to cause a stack overflow, and the performance benefit is seen no matter what order you add types.
 
Here is my version of HyperTypeDescriptionProvider (.NET 4.0... to compile this in earlier versions, replace occurrences of 'var' with their respective types and replace the attributes on BuildDescriptor with the ones in the original post):
public sealed class HyperTypeDescriptionProvider : TypeDescriptionProvider
{
    private static readonly Dictionary<Type, ICustomTypeDescriptor> descriptors = new Dictionary<Type, ICustomTypeDescriptor>();
    private static readonly Dictionary<Type, TypeDescriptionProvider> providers = new Dictionary<Type, TypeDescriptionProvider>();
 
    public static void Add(Type type)
    {
        lock (descriptors)
        {
            if (!providers.ContainsKey(type))
            {
                // determine if the base type was already added
                // if so, remove it before adding sub-type
                // (if a sub-type is added after its base type, infinite recursion occurs in GetTypeDescriptor())
                var baseFound = false;
                if (type.BaseType != null && providers.ContainsKey(type.BaseType))
                {
                    baseFound = true;
                    Remove(type.BaseType);
                }
 
                // add the provider for the type
                var provider = new HyperTypeDescriptionProvider(TypeDescriptor.GetProvider(type));
 
                TypeDescriptor.AddProvider(provider, type);
                providers.Add(type, provider);
 
                // initialize descriptor
                provider.GetTypeDescriptor(type);
 
                // if base type was removed, we can now add it back after building the sub-type descriptor
                if (baseFound)
                    Add(type.BaseType);
            }
        }
    }
 
    public static void Remove(Type type)
    {
        lock (descriptors)
        {
            TypeDescriptor.RemoveProvider(providers[type], type);
            providers.Remove(type);
            descriptors.Remove(type);
        }
    }
 
    public static void Clear()
    {
        lock (descriptors)
        {
            foreach (var provider in providers)
                TypeDescriptor.RemoveProvider(provider.Value, provider.Key);
            providers.Clear();
            descriptors.Clear();
        }
    }
 

    private HyperTypeDescriptionProvider()
        : this(typeof(object))
    { }
 
    private HyperTypeDescriptionProvider(Type type)
        : this(TypeDescriptor.GetProvider(type))
    { }
 
    private HyperTypeDescriptionProvider(TypeDescriptionProvider parent)
        : base(parent)
    { }
 
    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
    {
        lock (descriptors) {
            ICustomTypeDescriptor descriptor;
            if (!descriptors.TryGetValue(objectType, out descriptor)) {
                try
                {
                    descriptor = BuildDescriptor(objectType);
                }
                catch
                {
                    return base.GetTypeDescriptor(objectType, instance);
                }
            }
            return descriptor;
        }
    }
 
    [SecuritySafeCritical]
    [ReflectionPermission(SecurityAction.Assert, Unrestricted = true)]
    private ICustomTypeDescriptor BuildDescriptor(Type objectType)
    {
        // NOTE: "descriptors" already locked here

        // get the parent descriptor and add to the dictionary so that
        // building the new descriptor will use the base rather than recursing
        var descriptor = base.GetTypeDescriptor(objectType, null);
        descriptors.Add(objectType, descriptor);
        try
        {
            // build a new descriptor from this, and replace the lookup
            descriptor = new HyperTypeDescriptor(descriptor);
            descriptors[objectType] = descriptor;
            return descriptor;
        }
        catch
        {   // rollback and throw
            // (perhaps because the specific caller lacked permissions;
            // another caller may be successful)
            descriptors.Remove(objectType);
            throw;
        }
    }
}
 
Note that I call GetTypeDescriptor() in Add(). Normally, this wouldn't be necessary, as it is set up to do a sort of lazy building of descriptors, but this was the only way I found to prevent a stack overflow in the second error situation I mentioned. I also renamed the Clear(Type) method to Remove(Type) so that it is consistent with .NET naming conventions.

modified 6 Mar '13 - 9:37.

GeneralRe: Stackoverflow ExceptionmemberUberJono14 May '13 - 22:53 
Awesome stuff...
Firstly, thanks to Marc Gravell, and then to you, mattstermiller, for fixing the stack overflow...Thumbs Up | :thumbsup:
GeneralMethodAccessException with internal classmemberalexanderdev23 Dec '09 - 13:12 
First of all: thank you! Superb!
 
One question: how to get it working with internal classes if possible?
 
In the sample application changing the test class declaration to internal (internal class MySuperEntity : MyEntity {...}) causes the exception:
 
Unhandled Exception: System.Reflection.TargetInvocationException: Property accessor 'When' on object 'HyperPropertyDescriptorSample.MySuperEntity' threw the following exception:'HyperPropertyDescriptorSample.MySuperEntity.get_When()' ---> System.MethodAccessException: HyperPropertyDescriptorSample.MySuperEntity.get_When() ---> System.Security.SecurityException: Request failed.
 
However even if no way to go with internals i'll just change my DTOs to public...
GeneralRe: MethodAccessException with internal classmemberMarc Gravell24 Dec '09 - 0:21 
That should be pretty simple to fix, perhaps using `DynamicMethod` with the associated type. I'll take a look after xmas (otherwise I'm going to need surgical help to extract my laptop).
GeneralPlease helpmemberMember 159112723 Jan '09 - 21:57 
Dear Marc,
I am working on a .net add-in which requires access to the sealed class(.Net Base Class Microsoft.VisualBasic.Collection), I want to access key and value.
I could not find any help regarding this though u can see .net quick watch can access its value and as per my knowledge it does through reflection.
Can you provide me some help for two problems.
1) how to access nested internal class members.
2) how to access sealed class members.
(I know that if sealed class is derived from a base class then through virtual function inherited from base class you can access sealed class members but as in .net base class is object and its already sealed)
If possible provide me any code example.
Thanks in advance.
GeneralBug in a special case...memberMember 69538414 Nov '08 - 2:33 
Hi,
First thank you very much for this article.
 
We found just something that looks like a bug.
 
We have a list of classes X1, ... Xn using the HyperProp.
We have a control bound to the list typeof(X1),... typeof(Xn)
 
The binding ask the property descriptor of typeof(X1) that is a "System.RuntimeType".
Your class than try to call the BuildDescriptor also for "System.RuntimeType".
Finally the HP will do it foreach "System.RuntimeType" associated to each typeof(X1), ... typeof(Xn).
This doesn't work, unless we add the following line:
 
if (objectType.FullName.Equals("System.RuntimeType"))
return base.GetTypeDescriptor(objectType, instance);
 
at the beginning of
 
public sealed override ICustomTypeDescriptor GetTypeDescriptor(...)
 
i.e. we don't use the HP for the System.RuntimeType associated to the type X1; but we continue to use HP for the type X1.
 
Now it works also for this special case, but I would like to hear your opinion.
 
Thank you.
Claudio
QuestionIs HyperTypeDescriptionProvider applicable for sorting generic collection on dynamic property?memberGoran _17 Oct '08 - 14:58 
Hello, Marc Gravell, I have been searching for some code to speed up the performance of sorting generic collection based on dynamic property access. Acoring to the measurements you have done, it could be what I am looking for.
 
I have a simple code that creates 50,000 objects of CustomClass and adds them to a CustomBindingList<T> (inherits from BindingList<T>). In the ApplySortCore I call sort method and pass it a CustomPropertyComparer. All works well, but the problem is performance. For sorting 50,000 objects it needs 10 seconds, while when DataGridView bound to DataTable it needs less than a second (more like a 10th of second).
 
Is your method applicable for sorting? Before I go deep into the understanding how it works, I would like to know if it is good for what I want to achieve. When I set TypeDescriptorProvider to HyperTypeDescriptionProvider of my CustomClass, when it is about to display records it throws a series of exceptions which I catch in DataGridView DataError event:
 
{"WindowsFormsApplication1.Article.get_ID()"}
[System.MethodAccessException]: {"WindowsFormsApplication1.Article.get_ID()"}
Data: {System.Collections.ListDictionaryInternal}
HelpLink: null
InnerException: null
Message: "WindowsFormsApplication1.Article.get_ID()"
Source: "Hyper.ComponentModel.dynamic"
StackTrace: " at _c1.GetValue(Object )\r\n at System.Windows.Forms.DataGridView.DataGridViewDataConnection.GetValue(Int32 boundColumnIndex, Int32 columnIndex, Int32 rowIndex)"
TargetSite: {System.Object GetValue(System.Object)}
 
Thanks in advance,
Goran
AnswerRe: Is HyperTypeDescriptionProvider applicable for sorting generic collection on dynamic property?memberMarc Gravell17 Oct '08 - 22:10 
Given the numbers you quote, I wonder if you simply haven't disabled notifications during the sort?
 
HyperDescriptor certainly could be used that way; I've done a number of sortable binding list implementations, for example (also shows disabling notifications):
http://groups.google.co.uk/group/microsoft.public.dotnet.languages.csharp/msg/2b7528c689f9ef84
Since it uses PropertyDescriptor etc, it should be fully compatible with HyperDescriptor.
 
Marc
GeneralRe: Is HyperTypeDescriptionProvider applicable for sorting generic collection on dynamic property?memberGoran _17 Oct '08 - 23:43 
Hello, Marc, I thank you for the response. Disabling notifications didnt make much difference, the result are very similar. I just copied whole code you have provided, created an AdvancedList<Article> and binded it to DatagridView. Article is a simple class with three properties.
 
When I tied applying HyperTypeDescriptionProvider with AdvancedList, it doesn't throw any exception anymore, it does fill all 50,000 rows with 3 columns, but all cells have null value, so I was unable to test it in combination with HyperTypeDescriptionProvider.
 
For the illustration purposes, when using List<article),>
list.Sort(delegate (Article a1, Article a2){return a1.ID.CompareTo(a2.ID);}); // property name hardcoded
 
needs 0.04 seconds. Is there nothing to do to improve performance?
GeneralRe: Is HyperTypeDescriptionProvider applicable for sorting generic collection on dynamic property?memberMarc Gravell18 Oct '08 - 0:34 
I would have to have a look; I can't do right now, but should be able to have a look by Monday. For completeness, which framework are you using: 2.0, 3.0, 3.5?
 
Marc
GeneralRe: Is HyperTypeDescriptionProvider applicable for sorting generic collection on dynamic property?memberGoran _18 Oct '08 - 0:41 
Marc, I cant thank you enough. I am extensively using framework 2.0, but testing and measurements have been done in 3.5.
 
I will also investigate during the weekend, but I cant say I am versed at it the way you are, judging by the code you write. Smile | :)
 
Thanks again,
Goran

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 20 Apr 2007
Article Copyright 2007 by Marc Gravell
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid