C# 为什么带有显式重载的Emit中的接口实现在public和non-public中表现不同?

C# 为什么带有显式重载的Emit中的接口实现在public和non-public中表现不同?,c#,.net,reflection,reflection.emit,C#,.net,Reflection,Reflection.emit,我一直在研究反射。发射很长一段时间了,但这次它就是没有意义。。。在我的程序中,我正在使用emit实现接口。例如: typeBuilder.AddInterfaceImplementation(intf); 因为我实现了多个接口,并且接口可以从其他接口继承,所以我消除了重复的方法/接口。(虽然这里没有相关内容,但我将在我的示例中使用一些著名的接口)。例如,如果我同时实现IList和IDictionary,它们都实现ICollection,我只实现一次ICollection 之后,我开始使用生成的

我一直在研究反射。发射很长一段时间了,但这次它就是没有意义。。。在我的程序中,我正在使用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);
请注意,我明确定义了方法重写。我这样做是因为名字可能会冲突。在上面的示例中,IList和ICollection都公开了计数getter(name=get\u 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,但由于一个小错误,我没有显式实现所有计数功能(使用DefineMethodVerride)。奇怪的是,CreateType现在告诉我“Count[…]没有实现。”-例如,它找不到充当覆盖的Count方法

然而,由于我使用defineMethodverride,我想知道为什么它一开始就有效?换句话说,“私有”错误是有意义的,但它在使用公共方法时起作用的事实并不重要

那么对于我的问题:为什么.NET隐式重写具有相同名称的公共方法,即使您显式地将该重写定义为对另一个方法的重写(在我看来,这就像.NET中的bug…)?为什么这样做有效?当您将方法公开为公共时,为什么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)

如果您有多个具有相同名称和签名的方法,则您的类型无效。如果要显式实现接口,通常会使用具有不同名称的私有方法(例如,C#编译器使用类似于
IList.Count的方法,而不是
Count

更新

经过你的更新,我现在发现我的诊断有点倒退。如果一个类被声明为实现一个接口,并且有一个接口方法没有匹配的方法重写,那么CLR将搜索具有相同名称和签名的方法。没有办法告诉CLR不要这样做(除了为该接口方法提供不同的特定重写),并且同一个具体方法可以通过设计重写多个接口方法。同样,将具体方法称为
Third
,并显式重写
First
Second
,也很好

更新2

我将尝试回答你的另一个问题,但“为什么”问题总是很难回答。首先,向接口添加方法是一个严重的破坏性变化,基本上永远不会发生-如果向接口添加新方法,那么所有实现该接口的类(如果接口是公共的,则可能由第三方开发)将中断,因为它们声称实现接口,但缺少方法


用一个具体的实现覆盖多个接口方法似乎没有什么坏处。通常,这些方法来自不同的接口,因此这是一种方便的方法,可以避免创建多个相同的实现(假设不同的接口方法具有相同的语义,因此通过单个方法重写它们是有意义的)。同样,CLR通过名称+签名查找方法也很方便,而不需要显式映射。您的案例基本上是这些更通用机制的一个非常奇怪的角落案例实例,其中来自同一接口的多个方法由一个插槽实现,一次通过显式重写,一次通过默认查找。这是非常奇怪的,CLR似乎不值得明确注意这个特定场景,特别是考虑到不太可能(或不可能)从C#source生成。在C#中编写相同代码时,定义公共方法e.x的行为不算.net的默认行为吗?如果是这样,则需要分别对IList.Count和IDictionary.Count执行重写。我想这样做,您不应该有名称冲突,因为IDictionary不继承IList,所以实现会有所不同。如果您在C#中这样做,它不会添加defineMethodverride。如果您单独实现它们,我看不到一种公开它们的方法。不过,还有其他语言要考虑,其中一些可能可以重写方法并给它们一个不同的名称(这是有效的),正如KVB所声明的,您将不得不使用ILIST。您将把方法本身定义为public,但将为IList.Count和IDictionary.Count指定methodInfo,这两个方法都是私有的。这样做,你
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");
    }
}