|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionI wanted to experiment with using .NET's 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 Using the Automatic Interface ImplementerUsing 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:
How it WorksI will now describe, step-by-step, how the interface implementer works. Introduction: Type ObjectsA powerful feature of .NET is that all types can be represented as objects at runtime. The The following example creates Type intType = typeof(int);
Type stringType = typeof(string);
Type dateTimeType = typeof(DateTime);
Type formType = typeof(Form);
The The InterfaceObjectFactory ClassThe A /// <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 Stepping into the New() methodThe /// <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 The first thing the method does is load a After calling Stepping into CreateTypeFor()
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 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 The first thing we will do is get the base class' constructor: // Create Constructor
ConstructorInfo baseConstructorInfo = typeof(object).GetConstructor(new Type[0]);
Next, we will make a 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 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. List<MethodInfo> methods = new List<MethodInfo>();
AddMethodsToList(methods, type);
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 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;
// 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 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)
{
// 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 Type createdType = typeBuilder.CreateType();
InterfaceImplementations[type] = createdType;
So that's it! Our newly-created class that implements an interface is ready for Building and Running the Provided Source CodeThe 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 ReadingJconwell has two articles that discuss more about the
His example of writing a class in C#, and de-compiling it could provide a useful technique for modifying how dynamically generated objects behave. HistoryMy 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
|
|||||||||||||||||||||||||||||||||||||||||||||||||||