我已经使用反射发射很长一段时间了,但这次它根本没有意义......在我的程序中的某个地方,我正在使用 emit 实现接口。例如:
typeBuilder.AddInterfaceImplementation(intf);
因为我正在实现多个接口,并且接口可以从其他接口继承,所以我会消除重复的方法/接口。(虽然它在这里不相关,但我将使用一些众所周知的接口作为我的例子)。例如,如果我同时实现 IList 和 I 词典,它们都实现 ICollection,而我只实现一次 ICect。
之后,我开始使用生成的方法和接口列表将方法添加到 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);
请注意,我显式定义了方法重写。我这样做是因为名字可能会发生冲突,例如。在上面的示例中,IList 和 ICollection 都公开了一个计数获取器(名称 = get_Count),这将导致名称冲突。
现在假设我在生成方法时使用名称“Count”。正如我之前注意到的,有几个从 IList 派生的接口实现了此属性。我感到困惑的是,显然现在“Count”也隐含地继承了其他Count方法 - 即使我没有将其定义为Concort...但前提是我将该属性公开为公共属性。(例如,特殊属性 = 方法属性。发生的事情是PEVerify会给出一个错误,但代码会运行得很好:
[IL]: Error: [c:\tmp\emit\Test.dll : Test::get_Count][offset 0x00000012] Method is not visible.
为了解决这个问题,我尝试更改specialAttributes=MethodAttributes。私有-但由于一个小错误,我没有显式地实现所有计数(使用defineMethodOrride)。奇怪的是,CreateType现在告诉我“Count〔…〕没有实现”-例如,它找不到充当覆盖的Count方法。
然而,由于我使用的是定义方法覆盖,我想知道为什么它一开始就有效?换句话说:“私有”错误是有意义的,它在使用公共方法时有效的事实不是IMO。
所以对于我的问题:为什么?NET隐式重写同名的公共方法,即使您显式地将重写定义为另一个方法的重写(这听起来像是。网...)?为什么会这样?当您将方法公开为public时,为什么PEVerify会给出错误?
更新
显然,PEVerify错误是不相关的:在将所有内容私有化并显式实现所有方法之后,pevery仍然给出相同的错误。错误与调用错误方法有关,例如:
// 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个(!)第二个方法被“自动地”覆盖,即使我显式地告诉。该方法实现第一个方法。
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");
}
}
如果您有多个具有相同名称和签名的方法,则您的类型无效。如果你想显式实现一个接口,那么通常你会使用具有不同名称的私有方法(例如,C#编译器使用类似IList.Count
而不是Count
的东西)。
更新
在您的更新之后,我现在看到我的诊断有些倒退。如果将类声明为实现接口,并且存在没有匹配方法重写的接口方法,则 CLR 将搜索具有相同名称和签名的方法。没有办法告诉 CLR 不要这样做(除了为该接口方法提供不同的特定重写之外),并且同一具体方法可以根据设计重写多个接口方法。同样,将具体方法称为第三
个并显式覆盖第一
个和第二个是完全可以的
。
更新2
我会尝试回答你的另一个问题,但“为什么”的问题总是很难。首先,向接口添加方法是一个严重的重大更改,基本上不应该发生 - 如果您向接口添加新方法,那么您将期望所有实现该接口的类(如果您的接口是公共接口,则可能由第三方开发)将中断,因为它们声称实现接口但缺少方法。
能够用一个具体的实现覆盖多个接口方法似乎没有什么缺点。通常这些方法来自不同的接口,因此这将是一种方便的方法,可以避免创建多个相同的实现(假设不同的接口方法具有相同的语义学,因此通过单个方法覆盖它们是有意义的)。同样,CLR通过名称签名而不是需要显式映射来查找方法也很方便。您的案例基本上是这些更通用机制的一个非常奇怪的角落案例实例,其中来自同一接口的多个方法由一个插槽实现,一次通过显式覆盖,一次通过默认查找。这很奇怪,CLR似乎不值得明确关注这个特定场景,特别是考虑到不太可能(或不可能)从C#源生成。
问题内容: 我正在尝试整理实现公共接口的对象列表。涉及3个类和1个接口: 社区 类(具有一种方法: List getPeople(); ) 人员 接口(具有一种方法: String getName(); ) 女生 班(实施人员) 男生 班(实施人员) 请参见下面的代码。 我想要一个看起来像这样的XML: 或可能: 到目前为止,我得到的是: 我意识到我可以将元素更改为其他名称,但是我希望元素名称成为
问题内容: 和之间有什么区别? 例如 : 要么 问题答案: 尽管您 确实 希望遵守约定,但在功能上没有什么区别(字节码将 完全相同 ),请访问JLS-8.3.1。场修饰符: __ 这将是奇怪的,看.. 我也建议您访问checkstyle。 编辑: 从同一页面链接到该部分: http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls
例如: 或
我们举个例子: 一个常见的解决方案是转移到一个抽象类,但是在我的具体案例中,我有一个枚举的接口,所以在这里不适用。我想这不是被忽略了,就是因为接口背后的原始想法,即它们是可用方法的“契约”,但我想我需要关于这是怎么回事的输入。 我读过“为什么Java 8接口方法中不允许使用”final“?”,其中说: 默认方法的基本思想是:它是具有默认实现的接口方法,派生类可以提供更具体的实现 与关联问题一样,由
我创建了一个类Doctor,它使用户能够创建一个Doctor对象。该类的实例变量之一是:private String Specialism。 我制作了第二个名为Research的类,它使用户能够制作一个研究对象。该类的实例变量之一也是:private String Specialism。
问题内容: Java 构造函数的文档说明: 注意:很少适合使用此构造函数。除非需要新实例,否则静态工厂通常是更好的选择。它可能会产生明显更好的时空性能。 如果是这样,为什么这个构造函数是公开的而不被弃用?是否有充分的理由使用此构造函数代替? 问题答案: Java 1.4中仅添加了它,因此似乎存在构造函数是为了向后兼容。 此票证解释了不弃用构造函数的原因: 由于该中断,API可能不推荐使用,因此,当