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   
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
GeneralRe: Is HyperTypeDescriptionProvider applicable for sorting generic collection on dynamic property?memberMarc Gravell18 Oct '08 - 0:43 
It sounds like I should limit myself to 2.0 features, then.
 
I will have a look and post back.
GeneralRe: Is HyperTypeDescriptionProvider applicable for sorting generic collection on dynamic property?memberGoran _18 Oct '08 - 0:50 
Thanks. Btw, wen you do some testing and think there are some stuff that exist in 3.5 that could help in speeding things up, then I would be grateful if you could just drop some pointer(s) to a class/interface to research, and I will bookmark it for the future reference.
GeneralRe: Is HyperTypeDescriptionProvider applicable for sorting generic collection on dynamic property?memberMarc Gravell18 Oct '08 - 2:14 
Here we go; results first:
 
First run is for JIT
SortableBindingList<T>: 7ms, 20 items
List<T>: 0ms, 20 items
Main run without HyperDescriptor
SortableBindingList<T>: 2433ms, 50000 items
List<T>: 47ms, 50000 items
Enabling HyperDescriptor...
First run is for JIT
SortableBindingList<T>: 0ms, 20 items
List<T>: 0ms, 20 items
Main run with HyperDescriptor
SortableBindingList<T>: 157ms, 50000 items
List<T>: 55ms, 50000 items
 
So a *definite* improvement - about 15 times quicker using the Hyper code...
 
Here's the full sample:
 
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using Hyper.ComponentModel;
public class SomeType
{
      public int Number { get; set; }
      public string Name { get; set; }
      public double Value { get; set; }
      public override string ToString() {return Name; }
}
 
static class Program
{
      static string GetString(Random rand)
      {
            const string fromSet = " abcdefghijklmnopqrstuvwxzy ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789";
            char[] chars = new char[rand.Next(5, 20)];
            for (int i = 0; i < chars.Length; i++)
            {
                  chars[i] = fromSet[rand.Next(0, fromSet.Length)];
            }
            return new string(chars);
      }
      static void Main()
      {
            Console.WriteLine("First run is for JIT");
            RunTest(20);
            Console.WriteLine("Main run without HyperDescriptor");
            RunTest(50000);
            Console.WriteLine("Enabling HyperDescriptor...");
            HyperTypeDescriptionProvider.Add(typeof(SomeType));
            Console.WriteLine("First run is for JIT");
            RunTest(20);
            Console.WriteLine("Main run with HyperDescriptor");
            RunTest(50000);
      }
      static void RunTest(int size) {
            Random rand = new Random(123456);
            SortableBindingList<SomeType> list = new SortableBindingList<SomeType>();
            for (int i = 0; i < size; i++)
            {
                  SomeType obj = new SomeType();
                  obj.Number = rand.Next(0, 1000);
                  obj.Value = rand.NextDouble();
                  obj.Name = GetString(rand);
                  list.Add(obj);
            }
            List<SomeType> vanilla = new List<SomeType>(list);
            IBindingList bindingList = list;
            PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(SomeType))["Value"];
            Stopwatch timer = Stopwatch.StartNew();
            bindingList.ApplySort(prop, ListSortDirection.Ascending);
            timer.Stop();
            Console.WriteLine("SortableBindingList<T>: {0}ms, {1} items", timer.ElapsedMilliseconds, size);
 
            timer = Stopwatch.StartNew();
            vanilla.Sort((x, y) => x.Value.CompareTo(y.Value));
            timer.Stop();
            Console.WriteLine("List<T>: {0}ms, {1} items", timer.ElapsedMilliseconds, size);
 
      }
}
 
class SortableBindingList<T> : BindingList<T>
{
      internal abstract class SortProxy : IComparer<T>
      {
            public static SortProxy Create(bool isAscending, PropertyDescriptor property)
            {
                  if (property == null) throw new ArgumentNullException("property");
                  return (SortProxy) Activator.CreateInstance(
                        typeof(SortProxy<>).MakeGenericType(typeof(T), property.PropertyType),
                        isAscending, property);
            }
            private readonly bool isAscending;
            private readonly PropertyDescriptor property;
            public bool IsAscending { get { return isAscending; } }
            public PropertyDescriptor Property { get { return property; } }
 
            protected SortProxy(bool isAscending, PropertyDescriptor property)
            {
                  this.isAscending = isAscending;
                  this.property = property;
            }
            public abstract int Compare(T x, T y);
      }
      internal sealed class SortProxy<TValue> : SortProxy
      {
            public SortProxy(bool isAscending, PropertyDescriptor property)
                  : base(isAscending, property)
            {
                  this.comparer = Comparer<TValue>.Default;
            }
            private readonly Comparer<TValue> comparer;
           
            public override int Compare(T x, T y)
            {
                  TValue xVal = (TValue)Property.GetValue(x), yVal = (TValue)Property.GetValue(y);
                  int result = comparer.Compare(xVal, yVal);
                  return IsAscending ? result : -result;
            }
      }
      SortProxy sortProxy;
      protected override bool IsSortedCore
      {
            get { return sortProxy != null;}
      }
      protected override void RemoveSortCore()
      {
            sortProxy = null;
      }
      protected override ListSortDirection SortDirectionCore
      {
            get { return sortProxy == null || sortProxy.IsAscending ?
                  ListSortDirection.Ascending : ListSortDirection.Descending; }
      }
      protected override PropertyDescriptor SortPropertyCore
      {
            get { return sortProxy == null ? null : sortProxy.Property; }
      }
      protected override bool SupportsSortingCore
      {
            get {return true;}
      }
      protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
      {
            sortProxy = SortProxy.Create(direction == ListSortDirection.Ascending, prop);
            bool oldNotify = RaiseListChangedEvents;
            try
            {
                  RaiseListChangedEvents = false;
                  List<T> list = new List<T>(this);
                  list.Sort(sortProxy);
                  this.ClearItems();
                  foreach (T item in list) this.Add(item);
            }
            finally
            {
                  RaiseListChangedEvents = oldNotify;
            }
            ResetBindings();
      }
}
GeneralRe: Is HyperTypeDescriptionProvider applicable for sorting generic collection on dynamic property? [modified]memberGoran _19 Oct '08 - 2:51 
Hi Marc, what can I say.... great performance improvement. Smile | :) The trick to pass a property value type to comparer really speeded things up (in addition to HyperTypeDescriptionProvider). Never seen an approach to handle this by creating an abstract base class comparer to serve as a "bridge" for switching type T.
 
I did change one bit of code, which also gave some improvements (aprox 3%)
 
changed
 
RaiseListChangedEvents = false;
List<t> list = new List<t>(this);
list.Sort(sortProxy);
this.ClearItems();
foreach (T item in list) this.Add(item);
 
to
 
RaiseListChangedEvents = false;
List<t> list = this.Items as List<t>;
list.Sort(sortProxy);
 
Another thing, regarding the empty cells I have been getting while binding datagridview to list - the issue arrised if CustomType is not public. In order to work, SomeType must be declared as public - why is that?
 
Now I am up to studying the code for HyperTypeDescriptionProvider. I like to know and understand what I am using. Once again, thanks for the effort and great code. Much appreciated.
 
Goran
 
modified on Sunday, October 19, 2008 9:05 AM

GeneralRe: Is HyperTypeDescriptionProvider applicable for sorting generic collection on dynamic property?memberMarc Gravell18 Oct '08 - 2:18 
Re 3.5, Expression... however, building a dynamic expression and compiling to a delegate (a Comparison in this case) is not trivial... However, IIRC there is (or will be soon) a "projection comparer" in the MiscUtil library I worked on with Jon Skeet.
 
http://www.pobox.com/~skeet/csharp/miscutil/

GeneralVB versionmemberMarcR.9 Sep '08 - 6:04 
Hello Marc,
 
Is there a VB version of your work?
 
Thank you!
 
Marc R.

GeneralRe: VB versionmemberMarc Gravell9 Sep '08 - 9:25 
No, but you should be able to compile the dll using either C# Express, or MSBuild (at the command line) if you don't already have a C# IDE installed. You could then reference the dll from your VB project.
 
Would that do?
QuestionWhat a gemmemberMember 271995720 Aug '08 - 23:41 
I was considering a change to my design, for potential performance reasons, until I discovered this article.
 
The increase in performance was dramatic (though my timings are not scientific).
 
For extreme testing I created a datatable of 100,000 rows with 50 string columns. 100,000 business objects were created, from the datatable.
 
The business object properties were mapped to the table columns via System.Attribute.
 
Original time ~ 50 seconds (using PropertyInfo Setvalue)
Using your hyperdescriptor method ~ 4 seconds
 
Cheers,
 
Tony Evans (A former colleague)
 
PS. Regards to all in the office
AnswerRe: What a gemmemberMarc Gravell21 Aug '08 - 1:45 
Hi again Tony - it has been a while!
 
Glad the code was useful - one of the "fun" (wrong word, perhaps) things I do on the train Poke tongue | ;-P
 
Hope all is well,
 
Marc
GeneralGetFields() for enums is not working anymorememberdascalr12 Jul '08 - 6:37 
Hi,
 
First of all: Great job!
But I run into a small problem, when I add my object to HyperTypeDescriptionProvider GetFields is not working anymore, it returns an empty array. I think enums are not handled correctly.
 
public enum UserType {guest, user, powerUser }
 
PropertyDescriptor propDest = propDest["UserType"];
Type tp = propDest.GetType();
FieldInfo[] fields = tp.GetFields(); // <--- here I get an empty array.
 
Do you know how this can be fixed?
 
Thanks,
 
Romy

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

Permalink | Advertise | Privacy | Mobile
Web01 | 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