我正在为属性生成动态代理。
生成的代理派生自我们要代理的类型。当代理需要访问其派生类型的(虚拟)属性时,OpCodes.Callvirt
无法使用 - 它会导致无限递归。因此,我们需要称OpCodes.Call
.我注意到,如果我有:
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; }
}
}
当我在从MyChild
派生的代理对象上发出OpCodes.Call
来调用get_Color
它会被正确调用,即使从技术上讲,此方法未在MyChild
上实现。
我打算编写一些代码来遍历类型层次结构,以MyParent
可以找到get_Color
实现的位置,并将该类型方法用于OpCodes.Call
,但似乎没有必要:
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();
然后
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);
不使用声明类型方法并改用此类型方法是否安全?
您通常不需要声明类型的实现。
大概您希望执行与 base
关键字对 C# 编译器执行的操作相同的操作。 C# 编译器实际上查找派生最多的父实现并直接调用它,但您正在做的事情也是完全合法的。
如果基类位于另一个程序集中,并且在代码生成运行后重新编译该程序集添加新的重写,则它们具有不同的行为。 有关更多详细信息,请参阅 Eric Lippert(C# 编译器主要开发人员之一)的这篇博客文章,其中解决了以下确切情况:
- 在中间放一个底座
此问题说明了针对当前方法的OpCodes.Call
与具有实际实现的最派生父级之间的行为差异:
- 与外部库中的基方法绑定的方法无法在"之间"处理新的虚拟方法
重申一下,您不想在 DeclaringType
中使用实现,这通常不是上述两个合理选择中的任何一个。