1

我已经使用 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");
    }
}
4

1 回答 1

2

如果您有多个具有相同名称和签名的方法,则您的类型无效。如果您想显式实现一个接口,那么通常您会使用具有不同名称的私有方法(例如,C# 编译器使用类似的东西IList.Count代替Count)。

更新

在您更新后,我现在看到我的诊断有点倒退。如果一个类被声明为实现一个接口并且有一个没有匹配方法覆盖的接口方法,则 CLR 将搜索具有相同名称和签名的方法。没有办法告诉 CLR 不要这样做(除了为该接口方法提供不同的特定覆盖),并且相同的具体方法可以覆盖多个接口方法,设计。Third同样,调用您的具体方法并显式覆盖Firstand也是非常好的Second

更新 2

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

能够用单个具体实现覆盖多个接口方法似乎没有什么缺点。通常,这些方法来自不同的接口,因此这将是一种避免创建多个相同实现的便捷方法(假设不同的接口方法具有相同的语义,因此通过单个方法覆盖它们是有意义的)。同样,CLR 可以方便地通过名称+签名查找方法,而不需要显式映射。您的案例基本上是这些更通用机制的一个非常奇怪的极端案例实例,其中来自同一接口的多个方法由一个插槽实现,一次通过显式覆盖,一次通过默认查找。这很奇怪,看起来不像'

于 2013-02-19T18:24:33.053 回答