Following on from a taster that I posted before: http://lordzoltan.blogspot.com/2010/09/pseudo-template-meta-programming-in-c.html, here goes with the next piece.
Firstly, the design I'm showing here can be applied to any reflection scenario - I've deployed it in various guises, including for storing meta-data about methods in a class, properties, and even for compiling dynamic methods that provide late-bound functions. The primary reason why this design, I feel, is particularly useful is that you're making the .Net type engine do all the hard work of storing all this meta-data for you.
I'll go through this by looking at a really simple scenario:
You want to write a system whereby you can reflect properties of a type that a developer writes in order to provide some form of functionality. Let us say that you want to write a dictionary state-bag wrapper, where a property is wrapped around an internal dictionary of values. What you want is to have a code-generation friendly way for developers to add such properties to their class with the minimum of fuss. So, retrieving a property becomes as simple as:
- public string SomeProperty
- {
- get { return base.GetValue("SomeProperty"); }
- }
You don't necessarily want to have the names in the state bag to be the same as the names of the properties, so you need to be able to allow the developer to specify, with the minimum of fuss, what the backer for the property is actually called.
The perfect solution to this is an attribute:
- [AttributeUsage(AttributeTargets.Property)]
- public class DictionaryPropertyAttribute : Attribute
- {
- public string Name { get; set; }
- }
Now we can use the attribute as follows (now making a class that we can use later):
- public class MyClass
- {
- [DictionaryProperty(Name = "PROP_SomeProperty")]
- public string SomeProperty
- {
- get { return base.SetValue("SomeProperty"); }
- }
- }
So far so good, but we need a base class to be called. What we're going to do here is to produce two levels of abstraction: an interface and a base class that we can inherit from. The base class will implement the interface to provide a simple implementation of a dictionary. Other implementations could wrap around a data row, ASP.Net ViewState or SessionState and so on.
- public interface IDictionaryProps
- {
- void SetValue(string name, string value);
- string GetValue(string name);
- }
For simplicity's sake we'll say that an implementation of GetValue must return null if a value with the supplied name doesn't exist. And SetValue should quietly overwrite existing values, while adding new ones.
Our base class, then, looks like this:
- public class DictionaryPropertyClass :
- Dictionary<string, string>, IDictionaryProps
- {
- public void SetValue(string name, string value)
- {
- this[name] = value;
- }
- public string GetValue(string name)
- {
- string toreturn = null;
- this.TryGetValue(name, out toreturn);
- return toreturn;
- }
- }
And that's it, now we can write the outer class, with a couple of minor adjustments, for the wrapped property that I outlined first, so that it inherits from DictionaryPropertyClass.
But wait, what about this mapping business? Well, that's where we enter the realm of static generics.
For anyone still getting to grips with generic types, they are basically classes with type parameters - unlike C++ templates, however, which are built at compile time in a template-style, the generic class itself (without any type parameters) is a type in its own right - except it cannot be used to create an instance.
TIP: For anyone already doing run-time wizardry with generics - try typeof(MyGeneric<>) - it returns a reference to the generic type rather than an instance of the generic type. If your generic has multiple parameters, then you simply include a comma for each additional parameter. typeof(MyGeneric<,,>), for example, returns a reference to the System.Type of a generic that takes three type parameters.
What we're going to do is to create a static generic class (i.e. we never actually create an instance of it) which is going to act as a container for the reflected property information for any type:
- public static class DictionaryClassMetaData<T>
- where T : class, IDictionaryProps
- {
- //stores the mappings for properties to dictionary keys for T
- private static Dictionary<string, string> _mappings;
- //obtains the mapping for a particular property by its name. Caution,
- //this is case-sensitive of course (use
- //StringComparer.CurrentCultureIgnoreCase in the constructor for
- //_mappings to avoid this problem if case-sensitivity isn't required).
- public static string GetKey(string propertyname)
- {
- if (_mappings == null)
- return null;
- string toreturn = null;
- _mappings.TryGetValue(propertyname, out toreturn);
- return toreturn;
- }
- //static constructor - a lot of debate about these - mainly because of
- //the dreaded TypeInitializationException when the constructor raises
- //an uncaught exception, but if you are careful with your boot-strap code
- //(and religious with try/catch blocks where necessary) you don't have
- //anything to worry about.
- static DictionaryClassMetaData()
- {
- BuildPropertyMap();
- }
- //the method that does all the work
- private static void BuildPropertyMap()
- {
- //get the property list for T
- //note the use of System.Linq here and a lambda expression that filters
- //the PropertyInfo objects returned from the GetProperties call so that
- //we only get those properties which have our DictionaryPropertyAttribute
- //note also the use of typeof(T) - allows us to reflect on the T that
- //is fed into the generic type parameter of the static class.
- //finally, note the use of the FlattenHierarchy binding flags - this
- //means that our T can inherit from other types that also declare mapped
- //properties and still be able to get the entire mapping for the whole type.
- IEnumerable<PropertyInfo> props = typeof(T).GetProperties(
- BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy).Where(
- pinfo => pinfo.IsDefined(typeof(DictionaryPropertyAttribute), true));
- Dictionary<string, string> tocreate = new Dictionary<string, string>();
- DictionaryPropertyAttribute attr = null;
- //now to build our internal dictionary, if any properties were found
- if (props == null)
- return;
- foreach (PropertyInfo pinfo in props)
- {
- attr = (DictionaryPropertyAttribute)pinfo.GetCustomAttributes(
- typeof(DictionaryPropertyAttribute), true)[0];
- //make sure the name is unique. If so, add it. If not, then some form of
- //error reporting should be performed.
- //a good implementation should have a structure within this static type
- //that can be used to obtain errors instead of
- //throwing exceptions
- if (tocreate.ContainsKey(attr.Name) == false)
- tocreate.Add(pinfo.Name, attr.Name);
- }
- //if any properties found, set the dictionary, otherwise don't do anything
- if (tocreate.Count != 0)
- _mappings = tocreate;
- }
- }
Believe it or not, that's basically it. From outside of our class MyClass, we can now interrogate property mappings with the following line of code:
- string valuekey = DictionaryClassMetaData<MyClass>.GetKey("SomeProperty");
The first time a call like this is made, the .Net runtime will fire the static constructor for DictionaryClassMetaData<MyClass> (remember it's not a proper type until it's actually got a generic parameter in it!), after which it calls the GetKey method. After that initial call, however, it knows that it doesn't need to call it again, so it just fires the method. What's more, the .Net runtime ensures thead safety for that call, too, so you don't have to worry about locking anything in a multithreaded environment.
How does this help us with the initial plan? At the moment, we could reimplement our SomeProperty property in MyClass as follows (only the get accessor code is listed now):
- return GetValue(DictionaryClassMetaData<MyClass>.GetKey("SomeProperty"));
Hmmm, smelly isn't it... What's more - the developer implementing their class needs to know about the meta class - which is no good.
This is where I admit that I've led us down a blind alley - the truth is that the original idea of using base.Foo(bar) as a methodology isn't going to cut it. Although, as you'll see in a minute, we could implement it that way, there is a better way that we can do it. Generic extension methods:
- public static class IDictionaryPropsExtensions
- {
- public static string GetMappedValue<T>(this T instance, string propertyname)
- where T:class,IDictionaryProps
- {
- string datakey = DictionaryClassMetaData<T>.GetKey(propertyname);
- if(datakey == null)
- return null;
- return instance.GetValue(datakey);
- }
- }
Now, we can rewrite our implementation of MyClass' SomeProperty as follows (this time, I'll include the full code):
- public class MyClass : DictionaryPropertyClass
- {
- [DictionaryPropertyAttribute(Name = "PROP_SomeProperty")]
- public string SomeProperty
- {
- get { return this.GetMappedValue("SomeProperty"); }
- }
- }
Note how the implementation now uses 'this' directly. This is a strange feature of extension methods: when inside class scope, you will not see extension methods defined for that type unless you explicitly use 'this' - this is because without the 'this', the intellisense (and presumably the compiler) has no way of knowing that you actually mean 'this'. This is different from instance members - because they are within scope at the point where you are writing the code (when inside a class definition), therefore they are implicitly available.
We're also using the 'this' as the type parameter for our GetMappedValue extension method. If you were to breakpoint the extension method and do a get on the property, you will see that 'T' has been implicitly fed into it as MyClass. This is how we are then able to invoke the static meta class without any real effort whatsoever.
And finally...
Now that you have a static meta class that you can invoke in a single line of code, you can also do it with reflection. Consider this line of extremely generic code, which could be thought of as being similar to how the databound controls in Windows Forms, WPF and ASP.Net get hold of values when using the 'DataTextField' and 'DataValueField' properties:
- public string GetObjectPropertyValue(IDictionaryProps instance, string propertyname)
- {
- }
So now we're faced with a slight problem - we can't talk directly to the meta data class, because we don't have a T to work with. If only we could dynamically invoke the meta class... Hopefully, if you didn't already know it, you have probably guessed that we can. This time we're using dynamic generics - and this is where my little tip earlier on of using typeof(SomeGeneric<>) comes in really handy:
- public string GetObjectPropertyValue(IDictionaryProps instance, string propertyname)
- {
- Type t = instance.GetType();
- Type tmeta = typeof(DictionaryClassMetaData<>).MakeGenericType(new Type[] { t });
- return (string)tmeta.InvokeMember("GetKey",
- System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static,
- null, null, new object[] { propertyname });
- }
An interesting note about this code - even when you access a static class via reflection in this way, .Net will still maintain it's rule of firing the static constructor just once. Also, I'll wager that the speed with which it can obtain the type instance after the initial call is pretty good.
You can (and I have) cache the method for future use in a Delegate stored within the instance (think 'Delegate.MakeDelegate(typeof(Func<string, string>), reflectedmethod);') - this speeds up future access a great deal, because the JIT can optimize away a lot of the method-jumping.
There are some truly crazy things I've done with this model and I might blog about them in the future when I get a chance - in the meantime I hope this provides satisfaction for your coding needs!
Comments
Post a Comment