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 (10)

  1. Nicholas Allen says:

    In Java I found explicit interface implementation useful enough to build my own support for it. It’s not an everyday feature but I can’t fault the .net designers for including it. Unfortunate that it conflicts with the way they choose to implement generics, but genericity is such a pervasive language feature that adding it on to an existing language is going to cause someone problems no matter how you design it.

  2. Nicholas: could you show me how you did it in java? Thanks!

  3. Nicholas Allen says:

    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.

  4. AT says:

    This is lame that this does not compile:

    namespace Net20

    {

    public interface IEnumerator<T>

    {

    T Current { get; }

    bool MoveNext();

    }

    public interface IEnumerator : IEnumerator<object>

    {

    override object Current { get; }

    override bool MoveNext();

    }

    class Two : IEnumerator

    {

    object IEnumerator.Current { get { return new object(); } }

    public bool MoveNext()

    {

    return true; // Keep moving 😉

    }

    }

    }

    IMHO, Then then CLR will see something like this –

    .property object Net11.IEnumerator.Current

    {

    .get instance object Net11.One::Net11.IEnumerator.get_Current()

    }

    it can figure out (by default) – that this correspond to the same that

    .property object Net20.IEnumerator<System.Object>.Current

    {

    .get instance object Net20.Two::Net20.IEnumerator<System.Object>.get_Current()

    }

    I.e. allow some kind of virtual interfaces.

    Currently only half of this supported – it’s allowed to redefine existing method once more in your interface using "new" , but there is no way to indicate that you simply steal existing method vftable entry – but willing to add simply an alias (like IEnumerator vs. IEnumerator<object> in your scanerion).

    BTW, This kind of aliasing can be by-default (i.e. you can reffer to method names on interfaces that does not declare them – but inherit from some others), but this can hide some errors or confuse users then they will declare both

    object IMyInterface.Clone() and object ICloneable.Clone().

    In the same time – this feature can be usefull then you will do some interface refactoring.

    P.S> With covariant types – you will have to add "override" keyword to interfaces too. So – current situation is somethat simplieid example – then new return type = original overriden one 😉

    P.P.S> Runtime support requered if you will need binary compatibily, for souce level compatibility – only hack in compiler requered.

  5. AT says:

    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.

  6. DrPizza says:

    "IEnumerator doesn’t have a .Current "

    But it does…. It has the one it inherits from IEnumerator<>.

    I think that explicit implementation is useful in exactly one circumstance (implementing multiple interfaces with name clashes), and it shouldn’t be used for anything more than that. Unfortunately, it seems that people tend to use it as a matter of course. Code broken by this should be rare, not common.

  7. Barry Kelly says:

    Explicit interfaces are definitely worth it. How else do you expect to resolve this situation:

    2005: Release Foo with method Bar.

    2006: MS releases IBar with method Bar, different signature.

    2007: You want to upgrade Foo to support IBar.

  8. Eric says:

    One common use of explicit interface implementation I’ve seen is to put different access modifiers on property get/set, i.e.:

    int ISomething.Foo

    {

    get

    {

    return mFoo;

    }

    }

    internal int Foo

    {

    get

    {

    return mFoo;

    }

    set

    {

    mFoo = value;

    // Do something

    }

    }

    This way internal clients can set the value but public ones can only get it, This is the most useful when the factory pattern is used to return the object as an ISomething.

    I would expect to see things like this going away with .Net 2.0 allowing different get/set access levels.

  9. Barry Kelly says:

    Eric’s point about different access levels is even more interesting when you consider that the interface itself might not be public.