Is it safe to use OpCodes.Call on a virtual method?

270 views Asked by At

I'm playing with generating dynamic proxies for properties.

A generated proxy is derived from a type that we want to proxy. When the proxy needs to access a (virtual) property on the type it's derived from, OpCodes.Callvirt can't be used - it leads to infinite recursion. Thus we need to call OpCodes.Call. I noted that if I have:

public class MyParent 
{
    protected string _name;
    protected string _color;

    public virtual string Name
    {
        get { return _name; }
        set { _name = value; }
    }
    public virtual string Color
    {
        get { return _color; }
        set { _color = value; }
    }
}

public class MyChild : MyParent
{
    public override string Name {
        get { return "42"; }
        set { _name = value; } 
    }
}

When I emit OpCodes.Call on the proxy object derived from MyChild to call get_Color it gets called correctly, even though technically this method is not implemented on MyChild.

I was going to write some code that traverses the type hierarchy down to MyParent where get_Color implementation can be found and using that type method for OpCodes.Call, but it appears this is not necessary:

var thisTypeMethod = property.GetGetMethod();
// I know that the next line technically is not correct because of non-virtual methods 
// and also *new* overrides. Assume I'm doing it correctly, not by property.Name 
// but by repeatedly calling MethodInfo.GetBaseDefinition()
var declaringTypeMethod = property.DeclaringType.GetProperty(property.Name).GetGetMethod();

and then

var proxyMethod = new DynamicMethod(thisTypeMethod.Name,thisTypeMethod.ReturnType, new Type[]{type},type,true);
var il = proxyMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Tailcall);
il.Emit(OpCodes.Call, thisTypeMethod);
il.Emit(OpCodes.Ret);

Is it safe NOT using declaringTypeMethod and using thisTypeMethod instead?

1

There are 1 answers

0
Ben Voigt On BEST ANSWER

You don't usually want the implementation from the declaring type.

Presumably you want to do the same thing that the base keyword would do with the C# compiler. The C# compiler actually looks up the most-derived parent implementation and directly calls it, but what you are doing is also perfectly legal.

They have different behavior if the base classes are in another assembly, and that assembly is recompiled adding new overrides after your code generation runs. For more details, refer to this blog post by Eric Lippert (one of the C# compiler principal developers) which addresses this exact scenario:

This question illustrates the difference in behavior between OpCodes.Call against the current method, and the most-derived parent with an actual implementation:

To reiterate, you do NOT want to use the implementation in DeclaringType, which in general is not either of the above two reasonable choices.