tags:

views:

252

answers:

1

I ran into a strange C# edge case and am looking for a good work-around.

There is a class that I do not control that looks like this:

namespace OtherCompany
{
    public class ClassIDoNotControl
    {
        public void SomeMethod(string argument)
        {
            Console.WriteLine((new StackFrame(1).GetMethod().Name));
        }
    }
}

I'd like to inherit from this class in a class I do control. Additionally, I'd like to specify an interface on it:

interface IInterfaceIDoControl
{
    void SomeMethod(string argument);
}

class ClassIDoControl : OtherCompany.ClassIDoNotControl, IInterfaceIDoControl
{
}

If all of these files are in the same assembly, everything works great:

namespace MyCompany
{
    class Program
    {
        static void Main(string[] args)
        {
            IInterfaceIDoControl i = new ClassIDoControl();
            i.SomeMethod("Hello World!"); // Prints "Main"
        }
    }
 }

But, if I move "ClassIDoNotControl" into another assembly, I don't get what I expected. Instead, I see "MyCompany.IInterfaceIDoControl.SomeMethod" for the output implying an extra stack frame.

The reason is that under the covers, the C# compiler is changing "ClassIDoControl" to look like this:

class ClassIDoControl : OtherCompany.ClassIDoNotControl, IInterfaceIDoControl
{
    void IInterfaceIDoControl.SomeMethod(string argument)
    {
        base.SomeMethod(argument);
    }
}

Is there a way to avoid this compiler-generated extra layer of indirection with explicitly implemented interfaces?

+17  A: 

Short answer: The CLR requires that all methods that implement an interface method must be virtual (Ecma 335 Partition II Section 12.1).

Long answer:

  • If the method in the base class is already virtual, then nothing extra is needed: the interface method can be bound to it.

  • If the method in the base class is not virtual, but in the same assembly, the sneaky compiler actually makes it virtual and final. Reflector confirms this. (“final” is the CLR terminology for “sealed” in C#.)

  • If the method in the base class is not virtual and in another assembly, then obviously the compiler can’t do this because it can’t modify the already-compiled assembly. Therefore, the only option here is to insert a redirect method that implements the interface method. Like all methods that implement an interface method, it too is marked virtual and final.

So the answer to your last question, “Is there a way to avoid this?”, is unfortunately no.

Timwi
Ah ha! I didn't notice that subtle change in the C# disassembly in Reflector, but you're right that it occurs in the IL view with that "virtual final" trick. I learned something new today.
Jeff Moser
[Me too ☺](http://meta.stackoverflow.com/questions/700/)
Timwi