Saturday, May 9, 2009

Reflection proxy class generator - or how to access non-public members and use internal types the faster way

This is a small utility that generates reflection proxy classes - i.e. classes that contain methods and properties matching all methods and properties (public and non-public) of specified .NET classes and calling the latter with reflection. So if you have the following .NET class:

class MyClass{
private void SomeMethod(int i, string s){}
}
the generated reflection proxy class will look something like:

public class MyClassProxy{
public void SomeMethod(int i, string s) { /* reflection call onto the underlying type's method */ }
}
So if you want to make use of some non-visible methods or do something with non-public types with reflection (which is not generally advisable but can be the last resort in many cases) and you are tired of using the awkward MethodInfo-s, PropertyInfo-s and the like, you can try the automatically generated proxy classes which expose hidden members and types and make them readily accessible.

Inspiration and implementation concerns

I should admit here that the idea for creating the proxy generator came to me while I was reflecting the code of the SharePoint solution generator (quite nice stuff one can see in it). What I noticed there was that there were 30 or so proxy classes that call on various methods and properties of the standard classes in the SharePoint object model. I am not sure whether the guys from Microsoft created these by hand or somehow automatically, but it was clear to me that there is lots of stuff that can be accessed only via reflection - since Microsoft themselves create and use reflection proxy classes (at least in the SharePoint domain). Basically the paradigm behind my proxy classes differs quite significantly from theirs at least in respect to the inner implementation and workings. One main concern of mine was that I wanted all the code to be located in the proxy class itself without dependancies to other custom classes e.g. helper classes, base proxy class, etc. The reason for that is transparent - it allows for very easy use of the generated proxies - you just copy the generated .cs file and don't depend on external custom assemblies or other code. And about the class generation implementation itself - I used the System.CodeDom API here - so in the path of the standard recommendations for doing this kind of job providing some good level of flexibility and (at least theoretically) .NET language independance. So no nasty string concatenations for code generation - but you can check the code to find out more.

How to generate

You should first download the generator's solution and build it. It contains two projects: Stefan.CodeDomGen - a class library with one main class - ProxyGenerator - the generator itself; and console - a small console application that calls the ProxyGenerator in the class library.
The console application expects one command line parameter - this is the path to the configuration XML file. The configuration XML file looks like:

<?xml version="1.0" encoding="utf-8" ?>
<project ProxyNamespace="test" ProxyCodeFile="test.cs">
<assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c">
<class Name="Microsoft.SharePoint.SPWeb" ProxyName="" />
<class Name="Microsoft.SharePoint.Library.SPRequest" ProxyName="" />
</assembly>
</project>

Most elements and attributes in it are self explanatory. In the project element - the ProxyNamespace attribute specifies the name of the namespace which will contain the generated proxy classes and the ProxyCodeFile attribute specifies the path of the output code file that will be generated by the tool. The assembly elements denote the assemblies that contain the classes to be proxied and the class elements - the classes to be proxied - note - you can specify public and non-public types here. The ProxyName attribute specifies the name of the proxy class that will be generated - if you leave this attribute blank, the proxy class name will look like this - [Original class name]Proxy - i.e. - the name of the original class with "Proxy" appended to it. And ... that's basically it - once you have the XML file set with all classes that you want to be proxied, just run the console application providing the path to the XML as a parameter and check the generated .cs file after the tool completes the generation.

How to use the proxies

You first need to take the generated .cs file and add it to the project in which you'll make use of the proxies. The project won't compile at first but don't get startled - you need to add references to all assemblies that contain the various types used throughout the proxy classes - in the case of the sample XML above you'll need to add references to the following assemblies - System.Configuration, Microsoft.SharePoint, Microsoft.SharePoint.Library, Microsoft.Web.Design.Server. If you wonder how to add references to the latter two assemblies here's how I do it (if you already know how skip the following sentence) - I locate the assembly I need in a command prompt window using the dir xxxx* /s command from within the c:\windows\assembly folder - because the latter is a special windows folder windows explorer shows a special view of its contents and hides the sub-folder structure that contains the assembly files. So once you've found the exact location of the assembly you just copy it to some temporary folder from which you will be able to reference it using the Visual Studio's Add Reference/Browse command.
And now you are ready to use the proxy classes. But first let me quickly list the extra members that the proxy classes have next to the members matching the methods and properties of the corresponding proxied classes.

- UnderlyingInstance property - each proxy instance contains an instance of the proxied class so that it can call methods on it - obviously - if this one is not set, the proxy won't work. Note here - if the proxied class is public, the type of the property will be the real type of the proxied class, otherwise its type will be just object.
- FromUnderlyingInstance method - static method that returns a proxy instance - it has one parameter which accepts an instance of the proxied type - the type of the parameter is the same as the type of the UnderlyingInstance property. This is how you can get a proxy from an existing instance of the proxied class. It is actually a shortcut to this - MyClassProxy proxy = new
MyClassProxy () { UnderlyingInstance = someInstance };. And if you wonder why there is no (or there is still no) implicit conversion operator for the proxied type you can check the comments in the generator code ;)
- CreateUnderlyingInstance static method - actually this may have several overloads - their signatures match the signatures of the various instance constructors of the proxied class - and they return an instance of the proxy class. So they provide the means to create instances of the proxy class which initialize the UnderlyingInstance property with creating a new instance of the proxied class using one of its constructors (internally via a ConstructorInfo). So if you don't have an instance of the proxied class and want to create one, using CreateUnderlyingInstance is the way to do it.

And last but not least - an important note about the types of the parameters and return types of the generated proxy methods - there're two rules here:
1 - if the type has a matching proxy type it is substituted with the latter
2 - if the type is not a public one and has no proxy type it is substituted with the object type (for obvious reasons).

So basically the implications of the above are that the proxied types are in a sense hidden by the proxy types and you'll use and manipulate proxies in parameters and return types rather than instances of the proxied classes. Still the interchange between the proxy class and the proxied class is easily realized with the use of the
UnderlyingInstance property and the FromUnderlyingInstance method.

What next

Here's a short list of improvements that can be added to the proxy generator:
1 - I intentionally didn't add support for member fields - no proxy wrappers are generated for them but in some scenarios it may be handy to have such.
2 - Now, method and property wrappers are added for all methods and properties in the proxied classes, but this results in huge proxy class size - may be the "en masse" approach can be changed (or complemented) with some selective approach, so that only specific members get proxied - these can be specified in the configuration XML.
3 - Now, proxies can be generated only for classes - but probably it will be reasonable that support for enumerations and probably delegates and interfaces is added (these will be useful primarily for non-public types).

No comments:

Post a Comment