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