Why does interface implementation in Emit with explicit overload behave different for public and non-public?

1

I've been working with Reflection.Emit for a long time, but this time it just doesn't make sense... Somewhere in my program, I'm implementing interfaces using emit. E.g.:

typeBuilder.AddInterfaceImplementation(intf);

Because I'm implementing multiple interfaces and interfaces can inherit from other interfaces, I de-duplicate methods/interfaces. (While it isn't related here I'm going to use some well-known interfaces for my examples). For example, if I implement both IList and IDictionary they both implement ICollection and I only implement ICollection once.

Afterwards, I start adding methods to the typeBuilder using the resulting list of methods and interface. Nothing fancy, just:

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);

Notice that I explicitly define the method override. I do this because names might clash, f.ex. in the above example both IList and ICollection expose a Count getter (name = get_Count), which will result in a name clash.

Let's now assume I use the name 'Count' while generating methods. As I noticed earlier, there are a couple of interfaces that derive from IList that implement this property. What I'm confused about is that apparently now 'Count' implicitly inherits from other Count methods as well - even if I don't define it as Override... but only if I expose the property as public. (e.g. specialAttributes = MethodAttributes.Public). What happens is that PEVerify will give an error, but the code will run just fine:

[IL]: Error: [c:\tmp\emit\Test.dll : Test::get_Count][offset 0x00000012] Method is not visible.

To solve this, I tried to change specialAttributes = MethodAttributes.Private - but because of a minor bug, I haven't implemented all Count things explicitly (using DefineMethodOverride). Strangely enough, CreateType now tells me that "Count [...] does not have an implementation." - e.g. it cannot find a method of Count that acts as override.

However, since I'm using DefineMethodOverride I'm wondering why it worked in the first place? In other words: the 'Private' errors make sense, the fact that it worked when using public methods do not IMO.

So for my questions: Why does .NET implicitly override public methods with the same name even if you explicitly define the override as an override for another method (this sounds to me like a bug in .NET...)? Why does this work? And why does PEVerify give the error when you expose the methods as public?

update

Apparently the PEVerify error is unrelated: after making everything private and implementation of all methods explicit, PEVerify is still giving the same error. The error has to do with calling the wrong method, e.g.:

// 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)

Still, this was merely a sidetrack, the questions remain.

Update +1

I've made what I consider a minimal testcase. This basically generates a DLL that you should inspect with your favorite tool. Note that I only implement 1 method here, not 2 (!) The second method is overridden 'automagically', even though I explicitly tell .NET that the method implements the first method.

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#
.net
reflection
reflection.emit
asked on Stack Overflow Feb 18, 2013 by atlaste • edited Jun 3, 2014 by JasonMArcher

1 Answer

2

If you have multiple methods with the same name and signature, then your type is not valid. If you want to explicitly implement an interface, then typically you would use private methods that have different names (e.g. the C# compiler uses something like IList.Count instead of Count).

UPDATE

After your update I now see that I had the diagnosis somewhat backwards. If a class is declared as implementing an interface and there is an interface method with no matching method override then the CLR will search for a method with the same name and signature. There is no way to tell the CLR not to do this (other than providing a different specific override for that interface method), and the same concrete method can override multiple interface methods, by design. Likewise, it would be perfectly fine for your concrete method to be called Third and to explicitly override both First and Second.

UPDATE 2

I'll attempt to answer your other question, but "why" questions are always hard. First of all, adding a method to an interface is a serious breaking change and should basically never happen - if you add a new method to your interface then you would expect all classes that implement that interface (which might be developed by third parties if your interface is public) will break since they claim to implement the interface but are missing a method.

There seems to be little downside to being able to override multiple interface methods with a single concrete implementation. Typically those methods would be from different interfaces, so this would be a convenient way to avoid having to create multiple implementations that are identical (supposing that the different interface methods have identical semantics, so that overriding them via a single method makes sense). Likewise, it's convenient for the CLR to look for methods via name+signature rather than requiring an explicit mapping. Your case is basically a very weird corner case instance of these more general mechanisms, where multiple methods from the same interface are being implemented by one slot, once via an explicit override and once via the default lookup. This is very strange, it doesn't seem like it's worth it for the CLR to explicitly look out for this particular scenario, especially given that it would be unlikely (or impossible) to generate from C# source.

answered on Stack Overflow Feb 19, 2013 by kvb • edited Feb 19, 2013 by kvb

User contributions licensed under CC BY-SA 3.0