我已经使用 Reflection.Emit 很长时间了,但这次它没有任何意义......在我的程序的某个地方,我正在使用 emit 实现接口。例如:
typeBuilder.AddInterfaceImplementation(intf);
因为我正在实现多个接口并且接口可以从其他接口继承,所以我对方法/接口进行了重复数据删除。(虽然这里不相关,但我将在我的示例中使用一些众所周知的接口)。例如,如果我同时实现 IList 和 IDictionary,它们都实现 ICollection,而我只实现 ICollection 一次。
之后,我开始使用生成的方法和接口列表向 typeBuilder 添加方法。没什么特别的,只是:
MethodBuilder mb = typeBuilder.DefineMethod(
name,
MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual |
MethodAttributes.Final | specialAttributes, CallingConventions.HasThis,
returnType,
parameterTypes);
// [...] emit code that doesn't really matter here
typeBuilder.DefineMethodOverride(mb, baseMethodFromInterface);
请注意,我明确定义了方法覆盖。我这样做是因为名字可能会发生冲突,f.ex。在上面的示例中,IList 和 ICollection 都公开了一个 Count getter (name = get_Count),这将导致名称冲突。
现在假设我在生成方法时使用名称“Count”。正如我之前注意到的,有几个从 IList 派生的接口实现了这个属性。我感到困惑的是,显然现在“Count”也隐式地继承自其他 Count 方法——即使我没有将其定义为 Override……但前提是我将属性公开为公共。(例如 specialAttributes = MethodAttributes.Public)。发生的情况是 PEVerify 会给出一个错误,但代码会运行得很好:
[IL]: Error: [c:\tmp\emit\Test.dll : Test::get_Count][offset 0x00000012] Method is not visible.
为了解决这个问题,我尝试更改 specialAttributes = MethodAttributes.Private - 但由于一个小错误,我没有显式地实现所有 Count 事物(使用 DefineMethodOverride)。奇怪的是,CreateType 现在告诉我“Count [...] 没有实现”。- 例如,它找不到充当覆盖的 Count 方法。
但是,由于我使用的是 DefineMethodOverride,我想知道为什么它首先会起作用?换句话说:“私人”错误是有道理的,它在使用公共方法时起作用的事实并不在 IMO 中。
所以对于我的问题:为什么.NET 会隐式地覆盖具有相同名称的公共方法,即使您将覆盖显式定义为另一个方法的覆盖(这对我来说听起来像是 .NET 中的一个错误......)?为什么这行得通?当您将方法公开为公共时,为什么 PEVerify 会给出错误?
更新
显然 PEVerify 错误是不相关的:在将所有内容设为私有并显式实现所有方法之后,PEVerify 仍然给出相同的错误。该错误与调用错误的方法有关,例如:
// Incorrect: attempt to call a private member -> PEVerify error
callvirt instance void [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::System.Collections.IDictionary.Remove(object)
// Correct: call the interface using a vtable lookup
callvirt instance void [mscorlib]System.Collections.IList::Remove(object)
尽管如此,这只是一个偏题,问题仍然存在。
更新 +1
我做了我认为最小的测试用例。这基本上会生成一个 DLL,您应该使用您喜欢的工具对其进行检查。请注意,我在这里只实现 1 个方法,而不是 2 个(!)第二个方法被“自动”覆盖,即使我明确告诉 .NET 该方法实现了第一个方法。
public interface IFoo
{
int First();
int Second();
}
public class FooGenerator
{
static void Main(string[] args)
{
CreateClass();
}
public static void CreateClass()
{
// Create assembly
var assemblyName = new AssemblyName("test_emit.dll");
var assemblyBuilder =
AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName,
AssemblyBuilderAccess.RunAndSave, @"c:\tmp");
// Create module
var moduleBuilder = assemblyBuilder.DefineDynamicModule("test_emit", "test_emit.dll", false);
// Create type : IFoo
var typeBuilder = moduleBuilder.DefineType("TestClass", TypeAttributes.Public);
typeBuilder.AddInterfaceImplementation(typeof(IFoo));
ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(
MethodAttributes.Public | MethodAttributes.HideBySig |
MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
CallingConventions.HasThis, Type.EmptyTypes);
// Generate the constructor IL.
ILGenerator gen = constructorBuilder.GetILGenerator();
// The constructor calls the constructor of Object
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Call, typeof(object).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, Type.DefaultBinder, Type.EmptyTypes, null));
gen.Emit(OpCodes.Ret);
// Add the 'Second' method
var mb = typeBuilder.DefineMethod("Second",
MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual |
MethodAttributes.Final | MethodAttributes.Public, CallingConventions.HasThis,
typeof(int), Type.EmptyTypes);
// Implement
gen = mb.GetILGenerator();
gen.Emit(OpCodes.Ldc_I4_1);
gen.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(mb, typeof(IFoo).GetMethod("First"));
typeBuilder.CreateType();
assemblyBuilder.Save("test_emit.dll");
}
}