Thursday, February 08, 2007

Yet weirder nuances with C# interfaces

Brennan has a post about the differences between implicitly and explicitly implementing an interface in C#. He's got screenshots and better code formatting than I've figured out how to do yet, so really just go look there. But in brief, if you have an interface like this:

 public interface IDog  
 {  
     void Bark();  
     void Run();  
     void Catch();  
 }
You have two ways you can implement it. Implicitly, which is what I'm used to seeing:

 public class Bulldog : IDog  
 {  
#region Implicit implementation of IDog
 
     // Implicit 
     public void Bark()  
     {  
         throw new NotImplementedException();  
     }  
 
     public void Run()  
     {  
         throw new NotImplementedException();  
     }  
 
     public void Catch()  
     {  
         throw new NotImplementedException();  
     }  
 
#endregion  
 } 
Or explicitly like this:

 public class Terrier : IDog  
 {  
#region Explicit implementation of IDog
 
     void IDog.Bark()  
     {  
         throw new NotImplementedException();  
     }  
 
     void IDog.Run()  
     {  
         throw new NotImplementedException();  
     }  
 
     void IDog.Catch()  
     {  
         throw new NotImplementedException();  
     }  
 
#endregion  
 }  


They both accomplish the same thing (sort of, here come the nuances). Brennan pointed out that the explicit implementation (Terrier) will not show intellisense for variables of type Terrier. But you can still pass it around to methods expecting an IDog. Bulldog behaves more like you would expect -- intellisense shows all the methods and you can still pass it anywhere you could pass an IDog.

So lately I've been playing with Reflector more than is healthy for a person. It prompted me to go out and read the CIL specification. (Well, I'm almost done with Partition I at any rate. Yes, I've become that kind of geek.)

So I compiled this little sample and dropped it into Reflector. Interesting. The "normal" implicit implementation in Bulldog.Bark:
.method public hidebysig newslot virtual final instance void Bark() cil managed
{
      .
maxstack 8
      L_0000: nop 
      L_0001: newobj instance void [mscorlib]System.NotImplementedException::.ctor()
      L_0006: throw 
}
And the explicit implementation:
.method private hidebysig newslot virtual final instance void com.navelplace.IDog.Bark() cil managed
{
      .override com.navelplace.IDog::Bark
      .
maxstack 8
      L_0000: nop 
      L_0001: newobj instance void [mscorlib]System.NotImplementedException::.ctor()
      L_0006: throw 
}
So everything after .maxstack 8 is identical, as you'd expect. That nop puzzles me. That's a CIL no-op (do nothing). I'm guessing it has to do with byte alignment. If I'm wrong I'd love somebody to explain that to me.

Anywho... before you wig out about looking at IL... what are the differences? The explicit version declares itself as a .override, and not the implicit version. They're both "virtual final instance void". I'll break that down: virtual and final have the same meaning as they do in C#, virtual because all methods from an interface are virtual, final meaning you could not override Bark() in a subclass of Bulldog or Terrier. CIL is a lot more statically-oriented, so "instance" means "not static". And void meaning, umm, void.

hidebysig and newslot are pretty obscure -- they have to do with overloading and overriding. I believe all .method's coming out of C# will be marked hidebysig, or at least I haven't stumbled across anything marked hidebyname yet. It means that foo() will hide base.foo() but not base.foo(int). With hidebyname, foo() would hide anything from base named foo, regardless of signature. Although "signature" includes more things than we're used to in C#.
newslot comes into play for overriding. Implementing an interface requires a new slot on the type, but overriding a virtual method on a class would not specify newslot.

But now the real difference, and the weird part. The explicit implementation is marked private, while the implicit implementation is marked public. This explains why the explicit implementation doesn't show up in intellisense -- it's a private member of the concrete class. But it's not just intellisense that thinks so. new Terrier().Bark() is a compile-time error. This would seem to violate the inheritance rules from the spec, since everything in an interface is public:
When a derived type overrides a virtual method, it can specify a new accessibility for the virtual method, but the accessibility in the derived class shall permit at least as much access as the access granted to the method it is overriding.
There would seem to be an exception made for interfaces. And notice the method that specifically says ".override" is the one apparently violating the accessibility rule. Haven't stumbled across the part of the spec that explains the reasoning. It doesn't violate the substitution principle, since anything expecting an IDog would get an implicit cast and the explicit implementations would become visible again. But it still seems weird.

4 comments:

Anonymous said...

see http://enginepuller.com

Arno said...

Hi Brian,

coincidently I have written about that some time ago, you might want to have a look at http://arnosoftwaredev.blogspot.com/2006/03/net-explicit-interface-implementations.html. On one hand, explicit interface implementations help to avoid naming collisions. On the other hand, this mechanism allows to "choose" which one of several methods with equal signature (all but the return type) will be invoked depending on the underlying reference's runtime type. There are several components in the .NET API where this is of importance, e.g. IDbDataAdapter.SelectCommand vs. SqlDataAdapter.SelectCommand.

Arno said...

Small correction, I meant "depending on the underlying reference's compile-time type", not "runtime type".

Anonymous said...

This is indeed strange and just ran into it and stumbled across this post. Thanks for posting the details. Programmatically this doesn't seem to make a lot of sense to me.