Protecting Writable Properties with an Interface Delegator






3.75/5 (5 votes)
This article explains the use and implementation of an InterfaceDelegator. You may also find it to be a good example on building dynamic types using the System.Reflection.Emit namespace.
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)
// this will pass
if (iReadOnly is TestClass1)
//this will fail
if(iReadOnly.Equals(tClass))
// this will pass
}
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)
{
// Build the parameter type array
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);
// MethodAttributes to change if the method comes from System.object
if (isSystem_Object)
{
// Unset the MethodAttributes.NewSlot bit for the
// MethodAttributes of the new method
methodAttributes &= ~MethodAttributes.NewSlot;
}
// MethodAttributes to change if the method is an interface method
else
{
// Unset the MethodAttributes.Abstract bit and add the Final bit
// for the MethodAttributes of the new method
methodAttributes &= ~MethodAttributes.Abstract;
methodAttributes |= MethodAttributes.Final;
}
// Create the method
MethodBuilder methodBuild = typeBuild.DefineMethod
(methodInfo.Name, methodAttributes,
methodInfo.CallingConvention,
methodInfo.ReturnType, paramTypes);
// Build the implementation of the new method
ILGenerator methodILGenerator = methodBuild.GetILGenerator();
// Push this onto the stack
methodILGenerator.Emit(OpCodes.Ldarg_S, 0);
// Push _innerObj onto the stack
methodILGenerator.Emit(OpCodes.Ldfld, innerFieldBuild);
// push the passed in arguments onto the stack
for (int i = 1; i <= numParams; i++)
methodILGenerator.Emit(OpCodes.Ldarg_S, i);
// Call the method
methodILGenerator.Emit(OpCodes.Callvirt, methodInfo);
// return
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
andTestOutParam
were not working. After taking a closer look at the IL that was generated, I discovered some problems with theMethodAttributes
being used. The call toDefineMethodOverride
was not needed. I also found that theMethodAttributes.NewSlot
needed to be stripped off for methods that were being defined fromSystem.object
. I have uploaded the changes to the source and the demo.