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:
You have two ways you can implement it. Implicitly, which is what I'm used to seeing:public interface IDog{void Bark();
void Run();
void Catch();
}
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
}
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 8L_0000: nopL_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..method private hidebysig newslot virtual final instance void com.navelplace.IDog.Bark() cil managed{.override com.navelplace.IDog::Bark.
maxstack 8L_0000: nopL_0001: newobj instance void [mscorlib]System.NotImplementedException::.ctor()L_0006: throw
}
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.
3 comments:
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.
Small correction, I meant "depending on the underlying reference's compile-time type", not "runtime type".
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.
Post a Comment