Background
I like to generate code at run-time. I did it for my remoting, ORM, duck-casting and game solutions (at least the first version) but, at all times, I needed to use IL directly.
Even if I do this a lot I consider the IL instructions hard to understand and, by the Reflection.Emit methods, very easy to write uncompilable code or code with serious bugs.
What I always wanted was to easily create new methods, properties, events and specially fields at run-time without needing to access the IL directly.
Previous Solutions
All of my previous solutions were based on Interfaces or abstract classes. The most generic one simple implemented all methods to call a single one, telling which method was really invoked and passing all parameters as an array.
That solution had many problems. The most important ones were:
- It created arrays all the time
- It boxed all value-type parameters
- It was very easy for the programmer to create a giant method with a lot of switches, so it was hard to maintain
- If new properties or methods were required that were not part of the interface, it was impossible to add them
There was also the duck-typing/structural typing solution. It avoided the extra arrays but it required an existing type with compatible methods, properties and events. In fact, it was only useful if you didn't have access to the original class to make it implement a given interface and wanted to generate a wrapper at run-time. I even tried to add automatic casts, but it didn't solve the problem if the types were incompatibles, so it was very rare to find a place to use it.
DelegatedTypeBuilder
My new solution is the DelegatedTypeBuilder
class, which is based on delegates (as the name says) and allows you to create as many methods, properties and events you want.
It is not directly bound to interfaces but I still recommend them, after all if you don't know which methods or properties you are going to call (and maybe load them from a list) you can probably live with a dictionary. But if you know, then use an interface in your code (it makes the code run faster than using the dynamic keyword or reflection, allows you to use code completion and show errors at compile time if you misspell something).
What the DelegatedTypeBuilder
really allows us to do?
- Add methods, which will then call a given delegate. Such delegate can be general-purpose (that is, it will receive all parameters in an array) or it can be specific to a given amount of parameters (avoiding the array allocation)
- Add properties, which can have a field or not. In any case, delegates can be given to get and set accessors (and will pass the field value or reference as parameter to the get/set accessor) or it can be auto-implemented if it does not use a different field type
- Add events, which need to call an action for the add and remove
- All delegates can receive an extra user-instance parameter, so you can store all the information you need in there
- Allows data-type conversions to be specified before creating the methods and properties, so you can create an integer property that is implemented by an action that receives a
string
- And allows methods to directly redirect to another method in the user-instance (or sub-instance through another delegate), using all the available data-type conversions, making it very easy to create adapters
- Conversion to the same type are allowed as an way to create simple validations or value corrections (like, for example, trimming all
string
s so they don't have extra spaces at the beginning or ending)
How the DelegatedTypeBuilder works
The DelegatedTypeBuilder
concept is easy to understand. The hard part is the IL generation.
The DelegatedTypeBuilder
builds a type that will receive an user instance and that has a static delegate list.
Each added method must receive a delegate to do the real work. Such delegate is added to a list (if it is not already there) and the IL is built to access that delegate and pass all the parameters.
If conversions or casts are needed, they are also done. Casts are done directly, while conversions will also register the delegate in the delegate list and will call it to do the conversion.
And, when building the type, the list of delegates is converted to an array, as arrays are faster to access (and easier to use from IL instructions).
AdapterGenerator
The DelegatedTypeBuilder
is very general-purpose. It already allows users to define new types without knowing how to create the IL instructions but it is still a "low-level" resource.
It forces the user to add everything by hand and, if the new object must implement an interface, it is the user responsability to create all the needed methods and properties, even if their work is to throw a NotSupportedException
.
Considering the data-type conversion capability, I decided that an AdapterGenerator
will be very useful, so I created this class.
Its work is to implement an interface and redirect it to a real object, converting all the data-types that don't match.
As an extra functionality, if the interface depends on the INotifyPropertyChanged
, it already implements all property sets with a call to the PropertyChanged
event.
LazyLoaderGenerator
Patterns exist because some problem is too common, but there is no way to solve it completely without writing repetitive code, so the pattern is created to tell how to do things the right way.
INotifyPropertyChanged
is one of the cases, but it is already solved by the AdaptorGenerator. Another common case is the Lazy pattern.
The .NET class library now has the Lazy class to help avoid part of the repetitive code. I already presented two classes as Lazy solutions (Lazy Alternatives - LazyAndWeak and BackgroundLoader), as I consider the Lazy class incomplete.
But even in my solution it is not possible to avoid repetition.
We need to:
- Create a private field for each property and initialize it
- Implement the property getter to return the
privateField.Value
- Usually, we give a delegate to initialize the lazy object, probably something like _Load("PropertyNameHere")
The problem then is not limited to the amount of code, but to how easy it is to make a mistake, as users usually copy and paste the code and change names, and may:
- Forget to change the property name (which is not that bad, as the compiler will show the error)
- Forget to change the field name (so the wrong value will be loaded)
- Forget to change or misspell the name in the
string
(in this case the error can only be seen at run-time)
So, why not avoid this too?
That's the purpose of the LazyLoaderGenerator
.
It simple implements an interface made of read-only properties using the right Lazy pattern.
It also supports an user instance if you need to get an extra info for the Lazy loaders and allows you to register Lazy-loaders for each property type (so string
s will be loaded differently than Images).
To make sure different properties of the same type receive the correct value, the property name is given to the loader action, so it can find an appropriate file (for example) with that name.
And, as I don't like the default Lazy class, you can configure it to be LazyAndWeak (so unused values can be collected), Lazy (the .Net one) or BackgroundLoader, so values are pre-loaded in the background.
For example, imagine that you want to put all the images for your buttons as "typed resources" to your application, but you only want to load them if needed.
You can create an interface like this:
public interface IApplicationResources
{
ImageSource CloseApplicationImage { get; }
ImageSource OpenFileImage { get; }
ImageSource SaveFileImage { get; }
}
And you will implement the loading only once. If the files use the property name + ".png" (for example) it will be really easy to implement.
Now, as I told about the userInstance. Imagine that you have large icons, medium icons and small icons.
You will create three instances of the IApplicationResources interfaces, one with the "Large" parameter, another with the "Medium" and another with the "Small". So, the delegate you will register can be implemented by a method like this:
public static readonly BitmapSource _LoadImage(string kind, string name)
{
string fullName = string.Concat("images\\", kind, "\\", name, ".png");
using (var stream = File.OpenRead(fullName))
{
var decoder = new PngBitmapDecoder(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
return decoder.Frames[0];
}
}
Sample
The sample code is far from a complete application. It is only a sample on how to use the AdapterGenerator
and the LazyLoaderGenerator
.
The sample itself does not use the DelegatedTypeBuilder
directly, but both the AdapterGenerator
and the LazyLoaderGenerator
do.
In the sample, the Model has a string
(UserName) and a char that's used as a boolean, holding the values 0 or 1.
The IModel
, which is an INotifyPropertyChanged
and will be implemented by the AdapterGenerator
, also has an UserName, but a string
to string
conversion is added to trim exceeding spaces and the bool to char and char to bool conversions are added to transform true into '1' and false into '0' and vice-versa.
In the Window, there is a copy of the TextBox
and CheckBox
to show when the bindings really work.
Then, at the bottom there is the lazy-loaders test. In fact I didn't have anything to load, so I made the Lazy code wait for 3 seconds (using Thread.Sleep
) only to simulate a slow loading and then return a simple string
.
I hope this is already enough to show the potential of the code and maybe on the future I'll add better samples.
Garbage Collection
Up to .NET 3.5 dynamic generated code couldn't be collected. So, even if we lose all the references to the generated code, it will be there until the AppDomain dies.
.NET 4.0 allows us to create collectible assemblies and the DelegateTypeBuilder use it if asked to.
Why when "asked to", why not "always"?
For example, if you create adapters for all your classes during the load time and want to keep them all the time, there is no need to make the assemblies collectible. This can speed things up, as all the needed adapter types will be there all the time.
But the real fact is the number of dynamic assemblies in the AppDomain. By default I only generate one non-collectible dynamic assembly for all generated types. When collectible, each DelegatedTypeBuilder instance creates his own dynamic assembly.
See the constructor of DelegatedTypeBuilder
and the property MustBeCollectible
in AdapterGenerator
and LazyLoaderGenerator
classes.
Run-time Created Code Versus Pre-Generation
Some editors or compile-time utils may generate code for you. So, which one is better?
That depends on a lot of factors. If you have everything you need to pre-generate the code, it is usually better, as the code will be already there when you run and you will not have problems when running in untrusted environments (like Silverlight).
Pre-generated code can also have the benefits of using direct references (instead of interfaces), may be extended directly and when taking advantage of partial classes unused partial methods are simple removed from the compilation.
But then I should say that code generated at run-time reduces the number of files visible to the developers, reduce the size of the application, as the code is only generated at run-time and completely avoids the chance of having out-dated files or changes to the invalid files, as they are never visible to the users.
Also, if you really need to build a type at run-time (from configuration files, for example), then pre-generated code is not an alternative.