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.
Bill has been a developer for a software company in Canonsburg PA for the past 7 years and a computer engineering student at the University of Pittsburgh prior to that.
Bill dreams of one day not having to develop code using COM, and finally being able to use .net.