Partager via


You've got to be kidding me!

In the spirit of the post on Covariant Return Types, and "Death by a thousand cuts", i thought I'd mention yet another intensely aggravating part of .Net development.  This one isn't about C# per se, but C# is affected by it.

I'm going to use a real world example here.

In the .Net FX v1.0 and v1.1 we had the type which i'm sure you all know and love:

public interface IEnumerator {

     object Current { get; }

     bool MoveNext();

}

 

Now, we come along to .Net FX v2.0 with it's introduction of the generic collections.  Ok, so we know that we definitely want an IEnumerator<T>, but now where do we fit it into the whole hierarchy.  Well, after giving it a moment of though we can say to ourselves: "well, IEnumerator allows you to enumerate a list of objects... a-ha!" and you come up with this new hierarchy:

public interface IEnumerator<T> {

     T Current { get; }

     bool MoveNext();

}

public interface IEnumerator : IEnumerator<object> {

}

 

Simple, clean, elegant.  IEnumerator cleanly fits into the new generic hierarchy as an enumerator of objects, and so all your old IEnumerator code will work with new code that expects generics.  There would seem to be no issue of backwards compatability because the IEnumerator interface is losing not methods.  Rather, the methods are still there just in a superinterface.  So if call .Current on an IEnumerator, you will get a good old object back, same as you used to.

But can we do this? No.  Why not?  Well, because one of the things we allow an implementor of an interface to do is "Explicit interface implementation".  This handly little feature which allows you to implement two different interfaces with the same members now turns out to the bane of anyone wanting to clean up their interfaces in a way that would normally be safe in many other languages.  Why is it now unsafe?  Well, imagine the following code:

class UserClass : IEnumerator {

     object IEnumerator.Current { get { ... } }

}

 

Once we move IEnumerator.Current into IEnumerator<T>.Current we suddenly break this code.  IEnumerator doesn't have a .Current and so you can't explicitly implement it that way.  instead you need to do:

class UserClass : IEnumerator {

     object IEnumerator<object>.Current { get { ... } }

}

 

And it's pretty much guaranteed that there are thousands (if not much much more) of cases where users have done exactly this.  So making this change ensures that we're destroying a ton of code through backwards compatibilty problems.

I've never been a fan of explicit interface implementation, and here's one more reason why.  When i did java development it just never was the case that this would have been helpful.  And, while i can acknowledge that it might turn up, it doesn't seem like the pains it causes are worth it.  I also think that much of it is due to the old style MS Com development where you have one object implementing like 50 interfaces.  In java and .Net you just dont see that and this really was overkill considering all the problems it's introduced.

Sigh...

Comments

  • Anonymous
    March 23, 2005
    The comment has been removed
  • Anonymous
    March 23, 2005
    Nicholas: could you show me how you did it in java? Thanks!
  • Anonymous
    March 23, 2005
    What I did was create a utility class that uses Proxy objects to delegate the interface methods back to arbitrarily named methods in your class. An example is at

    http://jigcell.biol.vt.edu/svn/jigcell/jigcell/compare/impl/ProxyBuilder.java

    which I made for a particular project that had relatively simple needs. I've done some more complete versions (take a list of interfaces, create a single proxy object that implements them all) but they're not available online. The more complete version is nice in that you can replace "this" with the proxy object and everything still casts correctly.

    Interesting implementation detail I found out is that if you call a method by reflection more than about 20 times, the Sun VM will JIT the reflection invocation making it almost as fast as a native call.
  • Anonymous
    March 23, 2005
    The comment has been removed
  • Anonymous
    March 23, 2005
    P.P.P.S> If you need backward compatibility - CLR can do some hackery then it will see that some class implement interface from older version (using assembly name it refer) and adopt it for new one.
    As well this can be not CLR job - but installation/deployment time tool invocation.
  • Anonymous
    March 23, 2005
    The comment has been removed
  • Anonymous
    March 23, 2005
    The comment has been removed
  • Anonymous
    March 23, 2005
    The comment has been removed
  • Anonymous
    March 25, 2005
    Eric's point about different access levels is even more interesting when you consider that the interface itself might not be public.
  • Anonymous
    July 15, 2008
    PingBack from http://kassidy.onlinevidsdigestsubscription.info/401173.html