Skip to main content

Pseudo-Template Meta-Programming in C# - Part 2

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:

  1. public string SomeProperty
  2. {
  3.     get { return base.GetValue("SomeProperty"); }
  4. }

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:

  1. [AttributeUsage(AttributeTargets.Property)]
  2. public class DictionaryPropertyAttribute : Attribute
  3. {
  4.     public string Name { get; set; }
  5. }

Now we can use the attribute as follows (now making a class that we can use later):

  1. public class MyClass
  2. {
  3.     [DictionaryProperty(Name = "PROP_SomeProperty")]
  4.     public string SomeProperty
  5.     {
  6.         get { return base.SetValue("SomeProperty"); }
  7.     }
  8. }

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.

  1. public interface IDictionaryProps
  2. {
  3.     void SetValue(string name, string value);
  4.     string GetValue(string name);
  5. }

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:

  1. public class DictionaryPropertyClass :
  2.     Dictionary<string, string>, IDictionaryProps
  3. {
  4.     public void SetValue(string name, string value)
  5.     {
  6.         this[name] = value;
  7.     }
  8.     public string GetValue(string name)
  9.     {
  10.         string toreturn = null;
  11.         this.TryGetValue(name, out toreturn);
  12.         return toreturn;
  13.     }
  14. }

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:

  1. public static class DictionaryClassMetaData<T>
  2.     where T : class, IDictionaryProps
  3. {
  4.     //stores the mappings for properties to dictionary keys for T
  5.     private static Dictionary<string, string> _mappings;
  6.  
  7.     //obtains the mapping for a particular property by its name.  Caution,
  8.     //this is case-sensitive of course (use
  9.     //StringComparer.CurrentCultureIgnoreCase  in the constructor for
  10.     //_mappings to avoid this problem if case-sensitivity isn't required).
  11.     public static string GetKey(string propertyname)
  12.     {
  13.         if (_mappings == null)
  14.             return null;
  15.         string toreturn = null;
  16.         _mappings.TryGetValue(propertyname, out toreturn);
  17.         return toreturn;
  18.     }
  19.  
  20.     //static constructor - a lot of debate about these - mainly because of
  21.     //the dreaded TypeInitializationException when the constructor raises
  22.     //an uncaught exception, but if you are careful with your boot-strap code
  23.     //(and religious with try/catch blocks where necessary) you don't have
  24.     //anything to worry about.
  25.     static DictionaryClassMetaData()
  26.     {
  27.         BuildPropertyMap();
  28.     }
  29.  
  30.     //the method that does all the work
  31.     private static void BuildPropertyMap()
  32.     {
  33.         //get the property list for T
  34.         //note the use of System.Linq here and a lambda expression that filters
  35.         //the PropertyInfo objects returned from the GetProperties call so that
  36.         //we only get those properties which have our DictionaryPropertyAttribute
  37.         //note also the use of typeof(T) - allows us to reflect on the T that
  38.         //is fed into the generic type parameter of the static class.
  39.         //finally, note the use of the FlattenHierarchy binding flags - this
  40.         //means that our T can inherit from other types that also declare mapped
  41.         //properties and still be able to get the entire mapping for the whole type.
  42.         IEnumerable<PropertyInfo> props = typeof(T).GetProperties(
  43.              BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy).Where(
  44.                                     pinfo => pinfo.IsDefined(typeof(DictionaryPropertyAttribute), true));
  45.         Dictionary<string, string> tocreate = new Dictionary<string, string>();
  46.         DictionaryPropertyAttribute attr = null;
  47.         //now to build our internal dictionary, if any properties were found
  48.         if (props == null)
  49.             return;
  50.         foreach (PropertyInfo pinfo in props)
  51.         {
  52.             attr = (DictionaryPropertyAttribute)pinfo.GetCustomAttributes(
  53.                 typeof(DictionaryPropertyAttribute), true)[0];
  54.             //make sure the name is unique.  If so, add it. If not, then some form of
  55.             //error reporting should be performed.
  56.             //a good implementation should have a structure within this static type
  57.             //that can be used to obtain errors instead of
  58.             //throwing exceptions
  59.             if (tocreate.ContainsKey(attr.Name) == false)
  60.                 tocreate.Add(pinfo.Name, attr.Name);   
  61.         }
  62.         //if any properties found, set the dictionary, otherwise don't do anything
  63.         if (tocreate.Count != 0)
  64.             _mappings = tocreate;
  65.     }
  66. }

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:

  1. 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):

  1. 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:

  1. public static class IDictionaryPropsExtensions
  2. {
  3.    public static string GetMappedValue<T>(this T instance, string propertyname)
  4.          where T:class,IDictionaryProps
  5.    {
  6.       string datakey = DictionaryClassMetaData<T>.GetKey(propertyname);
  7.       if(datakey == null)
  8.          return null;
  9.       return instance.GetValue(datakey);
  10.    }
  11. }

Now, we can rewrite our implementation of MyClass' SomeProperty as follows (this time, I'll include the full code):

  1. public class MyClass : DictionaryPropertyClass
  2. {
  3.     [DictionaryPropertyAttribute(Name = "PROP_SomeProperty")]
  4.     public string SomeProperty
  5.     {
  6.         get { return this.GetMappedValue("SomeProperty"); }
  7.     }
  8. }

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:

  1. public string GetObjectPropertyValue(IDictionaryProps instance, string propertyname)
  2. {
  3. }

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:

  1. public string GetObjectPropertyValue(IDictionaryProps instance, string propertyname)
  2. {
  3.     
  4.     Type t = instance.GetType();
  5.     
  6.     Type tmeta = typeof(DictionaryClassMetaData<>).MakeGenericType(new Type[] { t });
  7.     
  8.     return (string)tmeta.InvokeMember("GetKey",
  9.             System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static,
  10.             null, null, new object[] { propertyname });
  11. }

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

Popular posts from this blog

Asp.Net 2 and 4 default application pool generates CS0016 IIS7.5

Before I start – if you’ve found a bunch of other articles about this around the net, tried the fixes that are mentioned and still not getting any joy – then read on – you might find this solves your problem. Earlier today I discovered that when I run any ASP.Net 2 or 4 application through IIS7.5 using the default application pools (which use ApplicationPoolIdentity) on Windows 2008 R2 x64 I get an error message similar to this:   Server Error in '/MvcApplication31' Application. Compilation Error Description: An error occurred during the compilation of a resource required to service this request. Please review the following specific error details and modify your source code appropriately. Compiler Error Message: CS0016: Could not write to output file 'c:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\mvcapplication31\222b4fe6\4e80a86\App_global.asax.clb4bsnc.dll' -- 'The directory name is invalid. ' Source Error: [No relevant source

Serializing to attributes in WCF with DataContractSerializer

It’s a common problem – you want to return an object from a WCF service as XML, but you either want, or need, to deliver some or all of the property values as XML Attributes instead of XML Elements; but you can’t because the DataContractSerializer doesn’t support attributes (you’re most likely to have seen this StackOverflow QA if you’ve done a web search).  Most likely you’ve then migrated all your WCF service code to using the XmlSerializer (with all the XmlElement/XmlAttribute/XmlType attributes et al) – and you’ve cursed loudly. Well, I’m here to rescue you, because it is possible – and the answer to the problem is actually inferred from the MSDN article entitled ‘ Types supported by the Data Contract Serializer ’. The example I’m going to give is purely for illustration purposes only.  I don’t have a lot of time, so work with me! Create a new Asp.Net WCF service application, you can use Cassini as your web server (probably easier – otherwise you might have to enable Asp.Net

Shameless plug - Use the new JobServe Web API to search for jobs your way

As my signature states - I work for JobServe in the UK.  Over the past few months I have been working on a new REST API (using the ASP.Net Web API from pre-beta to RTM) and it is now in official public beta. Now of course, this isn't just so your good selves can start running your own job searches using whatever web-enabled client takes your fancy, but that is one of the cool benefits that has come out of it - and that's why we're calling it a public beta. At the time of writing, you can use the API to run almost any job search that you can on the website.  As you would expect, you can get the results in XML or JSON (also GET requests with search parameters in the query-string are supported as well).  Note that JSONP is not currently supported - but it's slated for a future release. Sounds great, how do I get in? In order to get cracking with this - you need to request an API token .  This is not an automated process but we should be able to get you set up in a