Invoke base method using reflection

I came across this problem when I was writing a proxy library. I wanted to invoke the method of the base class but was instead invoking the overriden version. I was able to find the answer on Stack Overflow. Here is the code sample of the problem.

class BaseClass {
    public virtual void Test() { 
        Console.WriteLine("Test() from BaseClass"); 
    }
}

class OverridingClass : BaseClass {
    public override void Test() { 
        Console.WriteLine("Test() from OverridingClass"); 
    }
}

public static void Main() {
    var d = new OverridingClass();
    typeof(BaseClass).GetMethod("Test").Invoke(d, null);
}

The output is Test() from OverridingClass but what I wanted is to call the version of Test() defined in BaseClass.

The answer on Stack Overflow was great. Basically he created a DynamicMethod that would do the job. All I did was take that and generalize it so that it could be used as a drop in replacement for Invoke. So I created the following extension method:

public static object InvokeNotOverride(this MethodInfo methodInfo, 
    object targetObject, params object[] arguments) {
    var parameters = methodInfo.GetParameters();

    if (parameters.Length == 0) {
        if (arguments != null && arguments.Length != 0) 
            throw new Exception("Arguments cont doesn't match");
    } else {
        if (parameters.Length != arguments.Length)
            throw new Exception("Arguments cont doesn't match");
    }

    Type returnType = null;
    if (methodInfo.ReturnType != typeof(void)) {
        returnType = methodInfo.ReturnType;
    }

    var type = targetObject.GetType();
    var dynamicMethod = new DynamicMethod("", returnType, 
            new Type[] { type, typeof(Object) }, type);

    var iLGenerator = dynamicMethod.GetILGenerator();
    iLGenerator.Emit(OpCodes.Ldarg_0); // this

    for (var i = 0; i < parameters.Length; i++) {
        var parameter = parameters[i];

        iLGenerator.Emit(OpCodes.Ldarg_1); // load array argument

        // get element at index
        iLGenerator.Emit(OpCodes.Ldc_I4_S, i); // specify index
        iLGenerator.Emit(OpCodes.Ldelem_Ref); // get element

        var parameterType = parameter.ParameterType;
        if (parameterType.IsPrimitive) {
            iLGenerator.Emit(OpCodes.Unbox_Any, parameterType);
        } else if (parameterType == typeof(object)) {
            // do nothing
        } else {
            iLGenerator.Emit(OpCodes.Castclass, parameterType);
        }
    }

    iLGenerator.Emit(OpCodes.Call, methodInfo);
    iLGenerator.Emit(OpCodes.Ret);

    return dynamicMethod.Invoke(null, new object[] { targetObject, arguments });
}

With this extension method, the only change you have to make to the original code is to change the call to Invoke() to InvokeNotOverride(). Like so:

typeof(BaseClass).GetMethod("Test").Invoke(d, null);

becomes:

typeof(BaseClass).GetMethod("Test").InvokeNotOverride(d, null);

This has worked for my purposes, the method could benefit from caching since I think generating a DyanmicMethod each time is probably going to eat up memory.

You can download the source code from here: MethodInfoExtensions.zip

Comments

  1. says

    Placing this to Visual Studio project I got compilation error in this line:
    var dynamicMethod = new DynamicMethod(“”, ReturnType, new Type[] { type, typeof(Object) }, type);
    Visual Studio couldn’t locate ReturnType variable. I suppose there should be methodInfo.ReturnType instead of ReturnType.

  2. Hristo says

    Hi Luis,

    Thank you for the really nice and useful article. Please note, however, that calling the extension method and passing a struct as an argument will fail because of the parameterType.IsPrimitive check. You can change it to parameterType.IsValueType. I even think that actually we can remove the if statements and directly use the OpCodes.Unbox_Any field. For reference types, it will eventually use the CastClass field: https://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.unbox_any(v=vs.110).aspx

    Hristo

Leave a Reply

Your email address will not be published. Required fields are marked *