Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C#
Article

Automatic Interface Implementer: An Example of Runtime Type Building

Rate me:
Please Sign up or sign in to vote.
4.58/5 (6 votes)
10 Jan 2008CPOL8 min read 54.1K   730   33   5
An example of run-time type building; given an interface, returns an object that automatically implements the interface.
Screenshot of running the unit test

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:

C#
public interface IMyInterface
{
    int Int { get; set;}
    long Long { get; set;}
    bool Bool { get;set;}
    DateTime DateTime { get; set;}
    string String { get; set;}
}

Later:

C#
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:

  1. 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.
  2. Methods will do nothing.
  3. If the method has a return value, it will return a default value. This is usually 0, false, or null.
  4. 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.
  5. The interface implementer will throw an exception if it is used with a class.
  6. 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.
  7. 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.
  8. 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:

C#
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:

C#
/// <summary>
/// All of the types generated for each interface.  
/// This dictionary is indexed by the interface's type object
/// </summary>
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.

C#
/// <summary>
/// Returns an object that implement an interface, without the need to
/// manually create a type that implements the interface
/// </summary>
/// <typeparam name="T">T must be an interface that is public.</typeparam>
/// <returns>An object that implements the T interface</returns>
/// <exception cref="TypeIsNotAnInterface">Thrown if T is not an interface
/// </exception>
public static T New<T>()
    where T:class
{
        Type type = typeof(T);

        // If the type that implements the isn't created, create it
        if (!InterfaceImplementations.ContainsKey(type))
        CreateTypeFor(type);

        // Now that the type exists to implement the interface, 
        // use the Activator to create an object
        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:

C#
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:

C#
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:

C#
// Create 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.

C#
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.

C#
ILGenerator ilGenerator = constructorBuilder.GetILGenerator();
ilGenerator.Emit(OpCodes.Ldarg_0);                      // Load "this"
ilGenerator.Emit(OpCodes.Call, baseConstructorInfo);    // Call the base constructor
ilGenerator.Emit(OpCodes.Ret);                          // return

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.

C#
List<MethodInfo> methods = new List<MethodInfo>();
AddMethodsToList(methods, type);

AddMethodsToList() is recursive; this is so it can scan inherited interfaces.

C#
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:

C#
List<PropertyInfo> properties = new List<PropertyInfo>();
AddPropertiesToList(properties, type);

Again, recursion is used to handle inheritance:

C#
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.

C#
foreach (PropertyInfo pi in properties)
{
    string piName = pi.Name;
    Type propertyType = pi.PropertyType;

    // Create underlying field; all properties have a field of the same type
    FieldBuilder field =
        typeBuilder.DefineField("_" + piName, propertyType, FieldAttributes.Private);

    // If there is a getter in the interface, create a getter in the new type
    MethodInfo getMethod = pi.GetGetMethod();
    if (null != getMethod)
    {
        // This will prevent us from creating a default method for the property's getter
        methods.Remove(getMethod);

        // Now we will generate the getter method
        MethodBuilder methodBuilder = typeBuilder.DefineMethod(getMethod.Name,
            MethodAttributes.Public | MethodAttributes.Virtual, propertyType, 
            Type.EmptyTypes);

        // The ILGenerator class is used to put op-codes (similar to assembly) 
        // into the method
        ilGenerator = methodBuilder.GetILGenerator();

        // These are the op-codes, (similar to assembly)
        ilGenerator.Emit(OpCodes.Ldarg_0);      // Load "this"
        // Load the property's underlying field onto the stack
        ilGenerator.Emit(OpCodes.Ldfld, field);         
        ilGenerator.Emit(OpCodes.Ret);          // Return the value on the stack

        // We need to associate our new type's method with the 
        // getter method in the interface
        typeBuilder.DefineMethodOverride(methodBuilder, getMethod);
    }

    // If there is a setter in the interface, create a setter in the new type
    MethodInfo setMethod = pi.GetSetMethod();
    if (null != setMethod)
    {
        // This will prevent us from creating a default method for the property's setter
        methods.Remove(setMethod);

        // Now we will generate the setter method
        MethodBuilder methodBuilder = typeBuilder.DefineMethod
            (setMethod.Name, MethodAttributes.Public |
            MethodAttributes.Virtual, typeof(void), new Type[] { pi.PropertyType });

        // The ILGenerator class is used to put op-codes (similar to assembly) 
        // into the method
        ilGenerator = methodBuilder.GetILGenerator();

        // These are the op-codes, (similar to assembly)
        ilGenerator.Emit(OpCodes.Ldarg_0);      // Load "this"
        ilGenerator.Emit(OpCodes.Ldarg_1);      // Load "value" onto the stack
        // Set the field equal to the "value" on the stack
        ilGenerator.Emit(OpCodes.Stfld, field); 
        ilGenerator.Emit(OpCodes.Ret);          // Return nothing

        // We need to associate our new type's method with the 
        // setter method in the interface
        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.

C#
foreach (MethodInfo methodInfo in methods)
{
    // Get the return type and argument types

    Type returnType = methodInfo.ReturnType;

    List<Type> argumentTypes = new List<Type>();
    foreach (ParameterInfo parameterInfo in methodInfo.GetParameters())
        argumentTypes.Add(parameterInfo.ParameterType);

    // Define the method
    MethodBuilder methodBuilder = typeBuilder.DefineMethod
        (methodInfo.Name, MethodAttributes.Public |
        MethodAttributes.Virtual, returnType, argumentTypes.ToArray());

    // The ILGenerator class is used to put op-codes
    // (similar to assembly) into the method
    ilGenerator = methodBuilder.GetILGenerator();

    // If there's a return type, create a default value or null to return
    if (returnType != typeof(void))
    {
        // this declares the local object, int, long, float, etc.
        LocalBuilder localBuilder = ilGenerator.DeclareLocal(returnType);
        // load the value on the stack to return
        ilGenerator.Emit(OpCodes.Ldloc, localBuilder);
    }

    ilGenerator.Emit(OpCodes.Ret);                       // return

    // We need to associate our new type's method with the method in the interface
    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.

C#
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralGot My 5+ Pin
ring_024-Feb-10 1:25
ring_024-Feb-10 1:25 
GeneralRe: Got My 5+ Pin
GWBas1c25-Feb-10 9:05
GWBas1c25-Feb-10 9:05 
GeneralVery helpful! Pin
beat.kiener14-Feb-09 0:31
beat.kiener14-Feb-09 0:31 
GeneralIndexed properties Pin
Richard Deeming18-Jan-08 9:31
mveRichard Deeming18-Jan-08 9:31 
GeneralCool Pin
Steve Hansen10-Jan-08 23:31
Steve Hansen10-Jan-08 23:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.