Introduction
The InterfaceDelegator is a static class that can build a dynamic type that implements any interface passed in and forwards all the calls to an internal object that implements that interface. I created the InterfaceDelegator so I could protect myself and others from downcast errors. In the following example, you see a hack that I unfortunately I see often.
IReadOnly iRoObj = db.GetEntry(index);
((RealObjectType)iRoObj).Value = 1000;
Using the InterfaceDelegator, you can generate a wrapper object that users will not be able to downcast to your object type.
Using the Code
The following example shows how to create an InterfaceDelegator and how it is expected to behave:
public interface ITestClassReadOnly1
{
int SomeInt{ get;}
void TestOutParam(int inInt, out int outInt);
}
public interface ITestClassReadOnly2 : ITestClassReadOnly1
{
string SomeString { get;}
}
public class TestClass1 : ITestClassReadOnly2 {...}
static void Main(string[] args)
{
TestClass1 tClass = new TestClass1();
ITestClassReadOnly2 iReadOnly =
InterfaceDelegator.InterfaceDelegator<ITestClassReadOnly2>.Create(tClass);
if(iReadOnly is ITestClassReadOnly1)
if (iReadOnly is TestClass1)
if(iReadOnly.Equals(tClass))
}
InterfaceDelegator.Create will create a dynamic class that implements TT and forwards the calls to internal instance of TT. If the argument TT is not an interface, the call will return null.
Points of Interest
The DefineMethod method is a good example of how the InterfaceDelegator emits code to a dynamic type in a dynamic assembly.
private static void DefineMethod(MethodInfo methodInfo, TypeBuilder typeBuild,
FieldBuilder innerFieldBuild, bool isSystem_Object)
{
ParameterInfo[] methodParamInfos = methodInfo.GetParameters();
int numParams = methodParamInfos.Length;
Type[] paramTypes = new Type[numParams];
for (int i = 0; i < numParams; i++)
paramTypes[i] = methodParamInfos[i].ParameterType;
MethodAttributes methodAttributes = (methodInfo.Attributes);
if (isSystem_Object)
{
methodAttributes &= ~MethodAttributes.NewSlot;
}
else
{
methodAttributes &= ~MethodAttributes.Abstract;
methodAttributes |= MethodAttributes.Final;
}
MethodBuilder methodBuild = typeBuild.DefineMethod
(methodInfo.Name, methodAttributes,
methodInfo.CallingConvention,
methodInfo.ReturnType, paramTypes);
ILGenerator methodILGenerator = methodBuild.GetILGenerator();
methodILGenerator.Emit(OpCodes.Ldarg_S, 0);
methodILGenerator.Emit(OpCodes.Ldfld, innerFieldBuild);
for (int i = 1; i <= numParams; i++)
methodILGenerator.Emit(OpCodes.Ldarg_S, i);
methodILGenerator.Emit(OpCodes.Callvirt, methodInfo);
methodILGenerator.Emit(OpCodes.Ret);
}
The resulting code will look something like this in C#. In this case, the System.object.Equals was passed in to the first argument of DefineMethod.
public override bool Equals(object obj)
{
return Equals(obj);
}
One other point this exercise has reminded me is to use caution when playing with generics and statics. For each new argument that is passed to a generic class, there will be a new instance of a static member variable. You can see an example of this with the _newDelegatorType member of the InterfaceDelegator class. When a different generic argument is passed in, _newDelegatorType will be null. This is also the reason for having the internal InterfaceDelegatorModuleBuilderProvider static class. Without this class, a new assembly would be built for each different generic argument passed into the InterfaceDelegator.
History
- 2nd April, 2006 - I decided to see if I could use this code on Linux. I attempted to run the provided demo using the mono 1.1.13.6 runtime. What I found was that I had some bugs. There was a difference in the output. The
Equals and TestOutParam were not working. After taking a closer look at the IL that was generated, I discovered some problems with the MethodAttributes being used. The call to DefineMethodOverride was not needed. I also found that the MethodAttributes.NewSlot needed to be stripped off for methods that were being defined from System.object. I have uploaded the changes to the source and the demo.