Click here to Skip to main content
Click here to Skip to main content

Protecting Writable Properties with an Interface Delegator

By , 1 Apr 2006
Rate this:
Please Sign up or sign in to vote.

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

License

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

About the Author

billholmes54
Web Developer
United States United States
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.

Comments and Discussions

 
GeneralIL vs. reflection Pinmembermeekmaak4-Apr-06 8:13 
QuestionRe: IL vs. reflection Pinmemberbillholmes544-Apr-06 8:30 
AnswerRe: IL vs. reflection Pinmembermeekmaak4-Apr-06 12:43 
Questionwhere oh where are your zips? Pinmemberfwsouthern1-Apr-06 17:17 
AnswerRe: where oh where are your zips? Pinmemberbillholmes541-Apr-06 17:37 

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

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

| Advertise | Privacy | Mobile
Web04 | 2.8.140421.2 | Last Updated 1 Apr 2006
Article Copyright 2006 by billholmes54
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid