Many months ago I wrote a blog discussing an issue we were having with the Implement-Interface smart tag concerning what we should do when we cannot perform an action that the user has requested. Well (like most unresolved issues) the topic has reared it's ugly head and we are faced with a couple more confusing situations.
The first problem is most evident due a recent change in the BCL concerning our core collections classes. Basically at the root of everything are the following two classes:
(the recent change was to make IEnumerable<T> implement IEnumerable). So why is this an issue? Well imagine you have typed the following:
and you then ask us to implement the interface implicitly. Well, as it turns out we can't implement that interface implicitly. If we did you'd end up with
which is illegal C# code as you are not allowed to have two methods with the same name as same parameter types. So how would a user normally resolve this where two interfaces conflict with eachother? In C# you would use explicit interface implementation and you would write the second method as:
So what do we do about it. Right now we're falling back to implementing one of the methods explicitly so that we don't produce broken code. In this case we're leaning toward that being more important than strictly following the letter of what was stated earlier in the smart tag. Other options that we could have done would be to produce the broken code (not very nice since you have to fix it up), pop up a message telling you that we did that (with all of the associated burden of more UI annoying you), or disabling the option to implement the interface implicitly (with all the associated confusion from the user as to why it was no longer an option).
The second problem is a little more subtle, and it's definitely not clear what we should be doing. Say you have the following code:
and you ask to implicitly implement IFoo. Currently our behavior is to dump the method in the 'Derived' class. Part of me thinks that this is broken behavior. 'Derived' already implements 'IFoo' through Base::DoIt, so it doesn't need any extra implementation. On the other hand, sometimes people actually want 'Derived' to reimplement the IFoo interface by hiding the member in the base class (by using the 'new' keyword). Consider the example above, but this time you have a fully fleshed out IFoo interface with many methods and 'Base' implements just one of them. A user might be very suprised after implementing all of IFoo's methods that the DoIt method is actually calling into the 'Base' class instead of the class they thought they were implementing it on.
A common pattern that occurs in my code is to have a deeply nested object hierarchy that mimics a deeply nested interface hierarchy. So you have something like:
So, for me if i'm asking to implement "IList" on "ArrayList" i definitely do not want stubs for every interface in that hierarchy generated. They've already been implemented so why would i want to implement them again? All that happens is I end up with massive code spew that i then have to clean up afterwards.
So I'm changing the behavior slightly (and adding some configurability) and i wanted to get your thoughts on it. Here are the rules now:
- If you're implementing an interface member 'implicitly'
- If your type contains an implicit member that matches the interface member then we don't generate it
- If the option "check super types for matching members" is set and one of your super types contains an implicit member that matches the interface member then we don't generate it
- If one of your super types contains an implicit member that conflicts with the interface member then we generate it with the "new" keyword to indicate that hiding is occurring
- If one of the methods in your type conflicts with the interface then we explicitly implement i
- If you're implementing an interface member 'explicitly'
- If your type contains an explicit member that matches the interface member then we don't generate it
- If the option "check super types for matching members" is set and one of your super types contains an explicit member that matches the interface member then we don't generate it
By default we will be set to check super types when you're implementing implicitly and to not check super types when you're implementing explicitly.
To me this fits best with the model of "implement interface simply does the minimal work necessary to get your code actually implementing the interfaces it declares." However, if you feel that you're a person who wants this feature to stub out the entire interface and then you prefer to remove the possible unnecessary stubs, then there are options to all you to go back to that behavior.
How do you feel about that? Are we being totally boneheaded here? Is there an easy way to do the right thing and have everyone understand what's going on?