Introduction
I wanted to experiment with using .NET's TypeBuilder
class to automatically generate classes at runtime. For my experiment, I decided to implement a function, that, given an interface, returns a fully functional object that implements the interface. The programmer does not have to create a class to implement the interface.
In this article, I will first describe how one uses the automatic interface implementer. Later, I will describe how I used reflection and .NET's TypeBuilder
to, at runtime, dynamically create Types that implement interfaces.
Using the Automatic Interface Implementer
Using the Automatic Interface Implementer requires a pre-existing interface and a single line of code:
public interface IMyInterface
{
int Int { get; set;}
long Long { get; set;}
bool Bool { get;set;}
DateTime DateTime { get; set;}
string String { get; set;}
}
Later:
IMyInterface obj = InterfaceObjectFactory.New<IMyInterface>();
That's it! The Automatic Interface Implementer will return a working object for you to use without you having to write a class to implement the interface. This could be a great time-saver when writing a program that makes heavy use of interfaces.
There are some things to keep in mind when using the Automatic Interface Implementer:
- Properties will work, provided the interface declares both a getter and a setter. (This is what the above example does.) Properties that only have a getter will either return a default value or
null
. Properties that only have a setter are essentially no-ops.
- Methods will do nothing.
- If the method has a return value, it will return a default value. This is usually
0
, false
, or null
.
- The interface implementer is tested with properties, methods, generic interfaces, and interfaces that inherit from other interfaces. It is not tested with any other unanticipated features of .NET.
- The interface implementer will throw an exception if it is used with a class.
- When the interface implementer is part of a referenced module, it will only work with public interfaces. To use internal interfaces, include it within a module.
- The performance implications of using the interface implementer are minimal. At runtime, there will be a minor performance hit the first time it is used with an interface. (This is because it needs to generate a type for use with the interface.) Each subsequent use with the same interface will use the same type. Thus, using the interface implementer to build many objects of the same type will be almost as fast as creating a compiled class for the interface.
- Think carefully about using the interface implementer in production code. It has not been tested under reduced security contexts. It is intended for rapid application development.
How it Works
I will now describe, step-by-step, how the interface implementer works.
Introduction: Type Objects
A powerful feature of .NET is that all types can be represented as objects at runtime. The Type
class is used for this purpose. Type
objects can be passed as arguments to functions, inspected using Reflection, and used to create an object of the represented type.
The following example creates Type
objects for different classes and primitives:
Type intType = typeof(int);
Type stringType = typeof(string);
Type dateTimeType = typeof(DateTime);
Type formType = typeof(Form);
The typeof()
operator returns a Type
object for a given type. All objects also have a GetType()
method which returns the Type
object, although that is beyond the scope of this article.
The InterfaceObjectFactory Class
The InterfaceObjectFactory
is static
. This means that it's possible to use its methods without instantiating an instance of the class. Keeping the class static
saves memory and CPU cycles as all references to the class will use the same generated types.
A Dictionary
is used to keep track of generated types, as follows:
private static Dictionary<Type, Type> InterfaceImplementations =
new Dictionary<Type, Type>();
The Dictionary
is indexed by interfaces; specifically, the interfaces' Type
objects index it. In the dictionary, each interface will have a generated Type
object that implements the interface.
Stepping into the New() method
The New()
method, a generic method, is deliberately kept simple. It relies on the dictionary to have a type that implements the used interface. If the type does not exist, it calls a method to implement it.
public static T New<T>()
where T:class
{
Type type = typeof(T);
if (!InterfaceImplementations.ContainsKey(type))
CreateTypeFor(type);
return (T)Activator.CreateInstance(InterfaceImplementations[type]);
}
This method is generic. It allows the caller to use the method without performing any type-casting. The where
syntax is used to instruct the compiler to reject primitive types like int
, long
, bool
, etc. Unfortunately, we cannot instruct the compiler to reject classes; the CreateTypeFor()
method will throw an exception if it is not passed an interface, which we will just pass to the caller.
The first thing the method does is load a Type
object for the type specified in the generic argument. It then checks to see if the type is not in the dictionary, if it isn't, it calls the type generation method: CreateTypeFor()
.
After calling CreateTypeFor()
, we are sure that there is a Type
object present in the dictionary, or an exception is thrown. We then use the Activator
static class to generate an object that implements the interface, which is returned to the caller.
Stepping into CreateTypeFor()
CreateTypeFor()
is called to create a class, as a Type
object, to implement an interface. The first thing that it does is verify that the type is an interface:
if (!type.IsInterface)
throw new TypeIsNotAnInterface(type);
Fortunately, the above code is the only error checking we need to perform!
Next, we create a TypeBuilder
object that provides the functionality for building the class. We will give our class a name, make it public, specify that it's a class, and specify the interface that it implements:
TypeBuilder typeBuilder = ModuleBuilder.DefineType
("ImplOf" + type.Name, TypeAttributes.Class | TypeAttributes.Public);
typeBuilder.AddInterfaceImplementation(type);
In .NET, all classes need a constructor, including ones dynamically generated at run time. In order to be compatible with the Activator
, our constructor will take no arguments. All it will do is call the base class' constructor.
The first thing we will do is get the base class' constructor:
ConstructorInfo baseConstructorInfo = typeof(object).GetConstructor(new Type[0]);
Next, we will make a ConstructorBuilder
object. We will declare our constructor public, and state that it takes no arguments by using Type.EmptyTypes
.
ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
Type.EmptyTypes);
With the constructor declared, it is time to add our first op-codes to the class. Op-codes are similar to assembly, so anyone who has worked with assembly will probably feel that this is very familiar. We will ask the ConstructorBuilder
object to give us an ILGenerator
object, which we use to input the op-codes.
ILGenerator ilGenerator = constructorBuilder.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Call, baseConstructorInfo);
ilGenerator.Emit(OpCodes.Ret);
After building the constructor, we will need to obtain a list of methods to implement. Even though we will implement the interface's properties before we implement the methods, we get the methods first because all properties' getters and setters are also returned as methods. By persisting the list of methods while we implement the properties, we remove the properties' methods from the methods list.
List<MethodInfo> methods = new List<MethodInfo>();
AddMethodsToList(methods, type);
AddMethodsToList()
is recursive; this is so it can scan inherited interfaces.
private static void AddMethodsToList(List<MethodInfo> methods, Type type)
{
methods.AddRange(type.GetMethods());
foreach (Type subInterface in type.GetInterfaces())
AddMethodsToList(methods, subInterface);
}
Next, we will use similar logic for properties:
List<PropertyInfo> properties = new List<PropertyInfo>();
AddPropertiesToList(properties, type);
Again, recursion is used to handle inheritance:
private static void AddPropertiesToList(List<PropertyInfo> properties, Type type)
{
properties.AddRange(type.GetProperties());
foreach (Type subInterface in type.GetInterfaces())
AddPropertiesToList(properties, subInterface);
}
Now that we know the properties, it's time to implement them. We will iterate through each property using a foreach
. Each property will have a field named after the property, with an underscore as the name's prefix. If the property has a getter, we will implement it using a MethodBuilder
and an IlGenerator
in a similar way to how we generated the constructor. Likewise, if the property has a setter, we will also use a MethodBuilder
and IlGenerator
to implement the setter.
The op-codes for the getter will load the object and then the field onto the stack, and return it. The op-codes for the setter will load the object and the argument, "value" onto the stack, and then set the field. In both cases, the new method will need to be associated with the interface's getter or setter.
foreach (PropertyInfo pi in properties)
{
string piName = pi.Name;
Type propertyType = pi.PropertyType;
FieldBuilder field =
typeBuilder.DefineField("_" + piName, propertyType, FieldAttributes.Private);
MethodInfo getMethod = pi.GetGetMethod();
if (null != getMethod)
{
methods.Remove(getMethod);
MethodBuilder methodBuilder = typeBuilder.DefineMethod(getMethod.Name,
MethodAttributes.Public | MethodAttributes.Virtual, propertyType,
Type.EmptyTypes);
ilGenerator = methodBuilder.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Ldfld, field);
ilGenerator.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(methodBuilder, getMethod);
}
MethodInfo setMethod = pi.GetSetMethod();
if (null != setMethod)
{
methods.Remove(setMethod);
MethodBuilder methodBuilder = typeBuilder.DefineMethod
(setMethod.Name, MethodAttributes.Public |
MethodAttributes.Virtual, typeof(void), new Type[] { pi.PropertyType });
ilGenerator = methodBuilder.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Ldarg_1);
ilGenerator.Emit(OpCodes.Stfld, field);
ilGenerator.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(methodBuilder, setMethod);
}
}
Now that the getters and setters are created, the last complex step is to generate the "no-op" methods. For each method, we will get a list of arguments and a return type. This is used to generate a MethodBuilder
and IlGenerator
object, which are used in the same way that we created the constructor, getters, and setters.
On building the op-codes, if there is a return type, the method will declare an un-initialized field of the return type, and load it onto the stack. The method will return and it will be associated with the method as declared in the interface.
foreach (MethodInfo methodInfo in methods)
{
Type returnType = methodInfo.ReturnType;
List<Type> argumentTypes = new List<Type>();
foreach (ParameterInfo parameterInfo in methodInfo.GetParameters())
argumentTypes.Add(parameterInfo.ParameterType);
MethodBuilder methodBuilder = typeBuilder.DefineMethod
(methodInfo.Name, MethodAttributes.Public |
MethodAttributes.Virtual, returnType, argumentTypes.ToArray());
ilGenerator = methodBuilder.GetILGenerator();
if (returnType != typeof(void))
{
LocalBuilder localBuilder = ilGenerator.DeclareLocal(returnType);
ilGenerator.Emit(OpCodes.Ldloc, localBuilder);
}
ilGenerator.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(methodBuilder, methodInfo);
}
Now that the constructor, properties, and methods are generated, it is time to complete the class. This requires us to tell the TypeBuilder
that "we're done" by calling the CreateType()
method, and it requires us to store the newly-created class for later use.
Type createdType = typeBuilder.CreateType();
InterfaceImplementations[type] = createdType;
So that's it! Our newly-created class that implements an interface is ready for InterfaceObjectFactory.New<>()
to return! The newly created type is placed into the InterfaceImplementations
dictionary, just as InterfaceObjectFactory.New<>()
expects.
Building and Running the Provided Source Code
The provided source code is two projects and a solution for Visual Studio 2005. The programmer is encouraged to integrate InterfaceObjectFactory.cs into an existing class. (It may be useful to change the namespace.)
The demo program is a unit test, Memmexx.InterfaceImplementor.UnitTests. It is built to run under NUnit, an industry-standard testing Framework. As the demo uses basic NUnit functionality, it should run under most old and new versions of NUnit. The demo will look for nunit.core.dll and nunit.framework.dll in C:\Program Files\NUnit 2.4.3\bin. Assuming that you have a different version of NUnit or have NUnit installed in a different location, you will need to remove and re-add the references to nunit.core.dll and nunit.framework.dll.
Further Reading
Jconwell has two articles that discuss more about the TypeBuilder
:
His example of writing a class in C#, and de-compiling it could provide a useful technique for modifying how dynamically generated objects behave.
History
My original implementation used .NET's built-in C# compiler. I felt that such an approach would be too slow and un-reliable, so I re-wrote it with the TypeBuilder
.