Get *value* of custom property on object instance?

5.4k views Asked by At

Using .NET 4, C#

Let's say I have class Info that extends CustomTypeDescriptor. An instance of class Info has a dictionary of <string, object> pairs, loaded at runtime.

I'd like to be able to expose the dictionary keys as properties (so that each instance of Info has different properties). The values of the properties should be the corresponding values in the dictionary.

I got around to exposing properties:

    public override PropertyDescriptorCollection GetProperties()
    {

        var orig = base.GetProperties();
        var newProps = dictionary.Select( kvp => 
                       TypeDescriptor.CreateProperty(
                           this.GetType(), 
                           kvp.key, 
                           typeof(string)));
        return new PropertyDescriptorCollection(
                   orig.Cast<PropertyDescriptor>()
                   .Concat(newProps)
                   .ToArray());
    }

The problem is, how do I get their values?

var info = new Info(new Dictionary<string, object>{{"some_property", 5}};
var prop = TypeDescriptor.GetProperties(i_info)["some_property"];
var val = prop.GetValue(i_info); //should return 5

The only way I found to get control when prop.GetValue() is called was to override GetPropertyOwner(PropertyDescriptor pd), but the way I understand it, it expects me to return the instance of another type that has a matching real (compiled) property.

I'd like to be able to write the actual implementation of the property myself (for this example, return the value in the dictionary whose key matches the property name).

Is this possible?

2

There are 2 answers

0
Konstantin Oznobihin On BEST ANSWER

You need to make your own implementation of the PropertyDescriptor class overriding GetValue method. So instead of TypeDescriptor.CreateProperty you'll use new MyCoolPropertyDescriptor(dictionary, kvp.Key) or like.

Here is a sample of how it can be implemented:


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

namespace ConsoleApplication1
{
    internal sealed class MyCoolPropertyDescriptor : PropertyDescriptor
    {
        private Func<object, object> propertyGetter;
        private Action<object, object> propertySetter;

        public MyCoolPropertyDescriptor(
            string name,
            Func<object, object> propertyGetter,
            Action<object, object> propertySetter)
            : base(name, new Attribute[] {})
        {
            this.propertyGetter = propertyGetter;
            this.propertySetter = propertySetter;
        }

        public override bool CanResetValue(object component)
        {
            return true;
        }

        public override System.Type ComponentType
        {
            get { return typeof(object); }
        }

        public override object GetValue(object component)
        {
            return this.propertyGetter(component);
        }

        public override bool IsReadOnly
        {
            get { return false; }
        }

        public override System.Type PropertyType
        {
            get { return typeof(object); }
        }

        public override void ResetValue(object component)
        {
            this.propertySetter(component, null);
        }

        public override void SetValue(object component, object value)
        {
            this.propertySetter(component, value);
        }

        public override bool ShouldSerializeValue(object component)
        {
            return false;
        }
    }

    public sealed class Info : CustomTypeDescriptor
    {
        IDictionary<string, object> properties;

        public Info(IDictionary<string, object> properties)
        {
            this.properties = properties;
        }

        public override PropertyDescriptorCollection GetProperties()
        {
            var orig = base.GetProperties();
            var newProps = this.properties
                .Select(kvp => new MyCoolPropertyDescriptor(
                    kvp.Key,
                    o => ((Info)o).properties[kvp.Key],
                    (o, v) => ((Info)o).properties[kvp.Key] = v));

            return new PropertyDescriptorCollection(orig
                .Cast<PropertyDescriptor>()
                .Concat(newProps)
                .ToArray());
        }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            var info = new Info(new Dictionary<string, object>{{"some_property", 5}});
            var prop = TypeDescriptor.GetProperties(info)["some_property"];
            var val = prop.GetValue(info); //should return 5
            Console.WriteLine(val);
        }
    }
}

1
Enigmativity On

My understanding of CustomTypeDescriptor is that it allows databinding to expose extra properties to, say, a grid, that don't actually exist on the class. It's not something that extends the CLR so that your actual class exposes the property.

If you want actual CLR properties then you need to look at DynamicObject or ExpandoObject to get the kind of functionality that I think you're after.