Introduction
In this article I will discuss TypeBuilderLib, a library I
wrote to easily emit types and manage (cache) them. Type emission is an
alternative to code generation, having different advantages and inconveniences
compared to it.
TypeBuilderLib comes with four types of type emitters
out-of-the-box: Aspect Oriented Programming (AOP), Buffering, Indexer and
Mask. Those type emitters are reference implementations of a generic
framework to implement type emitters using the library.
The library also comes with a minimal suite of unit tests. I will use
the code of the unit tests as examples of usage of the library in this article.
Then I will explain the library architecture and how to extend it.
Background
The standard way to create a type is to code it: to write C# (or
other .NET language) code defining the type and compile it into an assembly.
This gives the programmer access to all the flexibility of programming language.
Some types are quite repetitive to create though and coding them can be
error-prone or simply non-efficient and hard to maintain. A typical example is an adapter (see the
wikipedia's article on the adapter pattern
on adapter pattern): in order to deal with an object through a well-defined
interface, you have to write a class wrapping that object and re-routing each
call to the wrapped object.
What if we could simply define an interface and then ask a library to generate
the adapter for us?
This way we wouldn't have to write the adapters or maintain them.
This is exactly what TypeBuilderLib does.
Another way to solve the problem would be to generate the code for the
adapters,
using code generation tools such as
Code Smith or MyGeneration.
TypeBuilderLib uses type emission, which offers an alternative to code generation with the
following advantages:
- No code is generated, so no generated code needs to be managed or
maintained.
Depending on the problem this could mean that a substantial amount of code
is eliminated.
- With code generation, each time the model is changed, the code generation must
be redone. TypeBuilderLib
emits the type at execution time, so the types are always up to date.
- It doesn't require installation of a tool in the developer environment.
- Types can be emitted on the fly using information available only at
runtime, which is impossible in the case of code generation.
The disadvantages of type emission are all related to the fact that type emission deals with Microsoft Intermediate Language
(MSIL) code:
- MSIL is a very low-level stack based language. A line of C#
can easily be translated into 12 MSIL instructions. That makes writing
MSIL a very tedious task.
- When bad code is emitted the error is only apparent when the code is
jitted and the error message is very generic. Visual debugging tools? Not
really.
In practice, type emission can be used side-by-side with
code-generation. This way, you can leverage the best tool for the task at
hand.
Using the code
As mentioned in the introduction, TypeBuilderLib comes with four
type emitters. We will look at examples of each of those.
Indexer
We will start with an example of an indexer. In this
example, we want to access generic DataRow
s in
a strongly type manner.
We first define the following interface:
public interface IPerson
{
string Name { get;}
int Age { get;set;}
}
In the following unit test, a variable table
, of type DataTable
, is pre-defined
with a schema corresponding to the interface:
[Test]
public void TestSetAge()
{
DataRow row = table.Rows.Add("Vincent", 33);
IPerson person = IndexerAdapterCache<IPerson>.GetInstance(row);
Assert.AreEqual(row["Age"], person.Age);
row["Age"] = 14;
Assert.AreEqual(row["Age"], person.Age);
person.Age = 5;
Assert.AreEqual(row["Age"], 5);
}
On the first line, we create a DataRow
. In the second line
we call IndexerAdapterCache<IPerson>.GetInstance
. This class belongs to TypeBuilderLib.
The method GetInstance
emits a class implementing the interface IPerson
, and for
each property of that interface, it implements call routing to
the DataRow
, casting the return value to the right type. GetInstance
also caches the emitted type, so that if the same request is done again, the type
won't be emitted a second time. The client code doesn't know about the generated type
(since it didn't exist at compile time) - it
simply handles a known interface. This is a pattern used throughout the
library: emitted code implements a known interface.
The rest
of the test simply tests that the Age
property
behaves normally and is actually getting and setting values in the data row.
IndexerAdapterCache<>
can be used with any object
exposing an indexer that takes a string
as a parameter and returns any type of
object (that is, any object obj
where obj["string"]
is valid), and
any interface exposing properties only.
Typical uses are:
-
DataRow
, as in the example
- Any generic data structure provided by a framework such as:
- Columns in a SharePoint list
- Properties in a Commerce Server product or profile
Property mapping
By default, the emitted type uses the property name to call the underlying
indexer object. For instance, in the previous example, the Name
property would be emitted as:
string IPerson.Name
{
get{return Indexer["Name"];}
}
In some cases it could be useful to have a property name not matching the index value. You can override this default
behavior by using the
attribute IndexerMapping
. For instance:
public interface IPerson
{
[IndexerMapping("name")]
string Name { get;}
[IndexerMapping("person_age")]
int Age { get;set;}
}
Mask
The Mask type emitter is useful to implement traditional adapters. Let
say we have the following interface:
public interface IPerson
{
string Name { get;}
int Age { get;set;}
void GrowAge(int offset);
}
and a class not implementing that interface but having the same method
signatures:
public class Person
{
private readonly string name;
private int age;
public Person(string name, int age)
{
this.name = name;
this.age = age;
}
public string Name
{
get { return name; }
}
public int Age
{
get { return age; }
set { age = value; }
}
public void GrowAge(int offset)
{
age += offset;
}
}
Let's say that this class is not under our control, so we cannot change it
to add an interface implementation.
The following unit test shows how to emit an adapter object bridging the class
to be exposed as a given interface:
[Test]
public void TestPerson()
{
Person person = new Person("Vincent", 33);
IPerson personProxy = MaskAdapterCache<IPerson>
.GetInstance<Person>(person);
Assert.AreEqual(person.Age, personProxy.Age);
personProxy.GrowAge(87);
Assert.AreEqual(person.Age, personProxy.Age);
}
Typical uses are:
- General objects outside your control (either part of another assembly or
generated by Visual Studio Designer) that you want to access through a given
interface.
- The
Profile
object generated by ASP.NET 2.0 via a web.config
configuration.
This object is accessible only to classes within the web project. If
you need to pass this profile to a component in another assembly, you can't.
You could write an interface corresponding to the profile and pass a
reference to an interface using an emitted adapter.
Buffering
The Buffering type emitter allows the creation of objects that can be used to
buffer other objects.
For instance, let's say we have the following interface:
public interface IPerson
{
string Name { get;}
int Age { get;set;}
}
and an object Person
implementing that interface. The following unit-test
shows how to create a type that could buffer that object:
[Test]
public void TestAge()
{
IPerson person = new Person("Vincent", 33);
IPerson buffer = BufferingAdapterCache.GetReadOnlyInstance(person);
Assert.AreEqual(person.Age, buffer.Age);
++person.Age;
Assert.AreNotEqual(person.Age, buffer.Age);
}
The test shows that when we alter the original object (++person.Age
),
the buffered object stays unaltered.
Here we created a readonly buffer instance, but you can also create a buffer
object that accept property sets. Readonly buffer object throws exception
when a property set is called.
Now why wouldn't you buffer a Person
object using another Person
object? You
should use this type emitter when the object you want to buffer cannot be kept
around for a long period of time, e.g., because it holds a connection to a database.
Typical use are:
- "Live objects", objects that can update themselves to a store via a
Update
method, e.g. list items in SharePoint.
- Objects that do not have a copy method (such as
Clone()
).
AOP
There are a lot of libraries available implementing Aspect Oriented
Programming (AOP, see
wikipedia's
article on AOP) in .NET. The AOP implementation of TypeBuilderLib
isn't meant to compete with those. It is more a showcase of what the
library can do.
AOP in TypeBuilderLib is implemented by wrapping objects with
another object implementing the same interface and intercepting every call.
Let's take an interface:
public interface IPerson
{
int Age { get;set;}
}
Let's say that we have a class Person
implementing this
interface. Let's define the following interceptor:
private class DetectInterceptor : IInterceptor
{
private bool intercepted = false;
public bool Intercepted
{
get { return intercepted; }
}
#region IInterceptor Members
object IInterceptor.InterceptCall(IMethodInvoker invoker)
{
intercepted = true;
return invoker.Invoke();
}
#endregion
}
What this object does is to implement the IInterceptor
interface
and simply marks the intercepted
field as true
when
a method or property is called. That is not a very useful interceptor, but
it allows us to test the library:
[Test]
public void TestPropertyGet()
{
IPerson person = new Person(33);
DetectInterceptor interceptor = new DetectInterceptor();
IPerson proxy = AopAdapterCache
.GetInstance<IPerson>(person, interceptor);
Assert.IsFalse(interceptor.Intercepted);
int age = proxy.Age;
Assert.AreEqual(person.Age, age);
Assert.IsTrue(interceptor.Intercepted);
}
The test goes as follows: after attaching an interceptor to an object,
we check that the interceptor hasn't been called, then we do a get on the Age
property
and check that the returned age is correct (the same than the value returned by
the real object) and that the interceptor has been called.
Typical uses are:
- Logging of method calls
- Consistent exception handling mechanisms on objects
- Security layer on method calls
Converters
In many of the sample type emitters we might want to have some control over the type emission. The way
for the library to let us jump in and control the type emission is to put
attributes on members. Here we will talk about the converters and the
attribute TypeConverterAttribute
.
A classic example is an indexer type emitter, where for a given property,
the interface property type is an enum
and the underlying object exposes an
int
. This is quite typical when the underlying object
takes an object from a database. This would be the way to decorate the
interface:
public interface IDemand
{
[TypeConverter(typeof(EnumConverter<Status>))]
Status DemandStatus { get;}
}
You can pass a list of type converters to the TypeConverter
attribute, each converts the underlying object value to another value and pass
it to the next converter. Here we just use one. This converter is
provided out-of-the-box, but you can develop your own by implementing
ITypeConverter<F, T>
.
Typical use are:
- Converting strings or integers values to an
enum
.
-
Converting a
DBNull
able value to a null
able value (using
Nullable<T
> for value types or just null
for reference types).
-
Any type of value and/or type conversions.
Architecture of TypeBuilderLib
TypeBuilderLib is built upon the type emission capabilities of
the .NET Framework, found in System.Reflection.Emit
. TypeBuilderLib
is meant to:
- Ease type emission development
- Cache emitted types
This following UML diagram shows the most important types in the library:
TypeEmitterCache
and TypeEmitterBase
. Note that
I extended UML with the '#' symbol to show protected
scope.
I also color-coded the classes depending on their stereotypes.
TypeEmitterCache
has two methods: GetInstance
and GetDynamicType
. GetDynamicType
looks up for a type in the cache. If it can't find it, it asks the
type emitter to create one. GetInstance
calls GetDynamicType
and
instantiates the type. The primary function of
TypeEmitterCache
is therefore to coordinate the creation of types
to make sure that the same type doesn't get created twice, for performance
reasons.
TypeEmitterBase
is where all the logic of type emission is.
This logic is twofold:
- Representing a type to be created. It can be used as a key in a dictionary. Every
class derived from
TypeEmitterBase
overrides the object type
methods Equals
and GetHashCode
.
- Ability to emit a type with a
System.Reflection.Emit.ModuleBuilder
.
This is done by overriding the method EmitType
.
Each instance of an TypeEmitterBase
represents a recipe to emit
a type. The following diagram shows the hierarchy of type emitters
implemented in TypeBuilderLib.
TypeEmitterBase
being a rather large contract, a specialization
was made: TypeEmitterOneInterfaceBase
. This specialization emits types implementing one and only one interface. That
is the case of the four emitters implemented in the library. TypeEmitterOneInterfaceParamBaseClassBase
is a further
specialization allowing parameterization of the base type of the emitted types.
Each implementation of TypeEmitterBase
must implement the
emission of a type, which means emitting MSIL. This is a non-trivial and
frustrating task. Therefore, we want to limit the amount of logic we emit.
A good strategy for doing that is to put most of the logic in a base type that
we write in C#, derive the emitted type from this base type, and call the method
of the base type. This way, we only have calls to emit - no complicated
logic (e.g. if, then, else, switch, computation, etc.).
TypeEmitterOneInterfaceParamBaseClassBase
comes into play to
implement that strategy, allowing parameterization of the type emitter with the base
type it's using.
In the code sample, we have never seen calls to
TypeEmitterCache
. This is because we were always using a facade
to
TypeEmitterCache
. The following diagram shows the facades of
TypeEmitterCache
provided in TypeBuilderLib.
Each cache facade uses
TypeEmitterCache
and none exposes TypeEmitterBase
in its interface. TypeEmitterBase
is used internally when a method of a facade is called: an instance of a TypeEmitterBase
is created, parameters are set on it, and the EmitType
method is called on it.
Performance
Type emission is very efficient. We tested the creation of an AOP proxy
and it takes around 15 miliseconds. Of course, this penalty happens only
the first time a given type is created. The next calls for the same type
simply take the type from the cache.
How to extend TypeBuilderLib
To extend TypeBuilderLib, you need to implement three
components:
- A type emitter to emit a type.
- An emitter cache facade to create and manage type emitters.
- A base class with the basic logic of the type you want to emit.
Conclusion
We have discussed TypeBuilderLib, a library I
wrote to easily emit types and manage (cache) them. This library is useful out-of-the-box with the type emitters it provides
(AOP, Buffering, Indexer and Mask) or as a framework to develop new type
emitters.
A good way to understand the library is to look at the unit tests.
Using type emission can be a great alternative to code generation when
information available only at runtime is required to emit types, or just to
simplify project maintenance by eliminating a code generation phase in the
build process.