我已经和Reflection.Emit一起工作了很长时间,但这一次它没有意义......在我的程序的某个地方,我使用emit实现接口。 E.g:
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 ...但仅当我将属性公开为public时。 (例如,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隐式覆盖具有相同名称的公共方法,即使你明确地将覆盖定义为另一个方法的覆盖(这听起来像是一个错误。净...)?为什么这样做?当您将方法公开为公共时,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个(!)第二个方法被自动覆盖&#39;,即使我明确告诉.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");
}
}
答案 0 :(得分:2)
如果您有多个具有相同名称和签名的方法,则您的类型无效。如果要显式实现接口,那么通常使用具有不同名称的私有方法(例如,C#编译器使用类似IList.Count
而不是Count
的内容)。
<强>更新强>
更新后,我现在看到我的诊断有些倒退了。如果一个类被声明为实现一个接口,并且有一个没有匹配方法覆盖的接口方法,那么CLR将搜索具有相同名称和签名的方法。没有办法告诉CLR不要这样做(除了为该接口方法提供不同的特定覆盖),并且相同的具体方法可以通过设计覆盖多个接口方法。同样,您可以调用具体方法Third
并明确覆盖First
和Second
。
更新2
我会尝试回答你的另一个问题,但“为什么”问题总是很难。首先,向接口添加一个方法是一个严重的突破性变化,基本上永远不会发生 - 如果你向你的接口添加一个新方法,那么你会期望所有实现该接口的类(如果你的话可能由第三方开发)接口是公共的)将破坏,因为他们声称实现接口但缺少方法。
使用单个具体实现覆盖多个接口方法似乎没有什么缺点。通常这些方法来自不同的接口,因此这将是一种避免必须创建相同的多个实现的方便方法(假设不同的接口方法具有相同的语义,因此通过单个方法覆盖它们是有意义的)。同样,CLR可以方便地通过名称+签名查找方法,而不需要显式映射。你的情况基本上是这些更通用机制的非常奇怪的案例实例,其中来自同一接口的多个方法由一个插槽实现,一次通过显式覆盖,一次通过默认查找。这是非常奇怪的,似乎CLR明确地注意这个特定情况似乎是值得的,特别是考虑到从C#源生成不太可能(或不可能)。