Daniel Cazzulino's Blog : Evolving an API without breaking clients via extension methods, ObsoleteAttribute and EditorBrowsableAttribute: Part II

Evolving an API without breaking clients via extension methods, ObsoleteAttribute and EditorBrowsableAttribute: Part II

Due to a bug in VS intellisense (go vote for it!), my otherwise nice approach to API evolution doesn't quite work as I expected. Basically, regardless of whether you flag your new extension methods containing the obsolete APIs (read my previous post if that didn't make sense ;)) with EditorBrowsableState.Never, they will still show up in intellisense. Essentially, VS ignores the attribute altogether for extension methods.

You typically don't want that behaviors, as it would confuse users of your library to see legacy members (as extension methods) alongside the new ones:

image

A side effect of the approach in my previous post is that you need to add a reference to the new Legacy.dll assembly in order for your existing code to compile. This may be an issue for some users.

So, in order to overcome the bug and also improve the backwards compatibility, I gave the approach another twist.

Typically, you have basically two different scenarios when you want to evolve the API:

  • Renamed members should NEVER become visible, EVER. Existing code should continue to compile as-is if possible.
  • Existing functionality that you want to obsolete but still want to make visible to users in case they depend on its continued use, should become visible only when they add a reference to the new Legacy.dll assembly. But existing code should continue to compile as-is if possible.

Given the annoying editor browsable bug, we cannot fully control visibility through that. But we can leverage the situation and use extension methods on the Legacy.dll assembly to control the visibility of the main assembly members.

Turns out that when you provide an extension method with the same name as a hidden method in the target class, that hidden method becomes visible, and of course the compiler will always resolve to it rather than the extension method.

So, this would be the intellisense experience without the Legacy.dll assembly reference (note missing VerifyAll):

image

Note that the line just above the intellisense window invokes VerifyAll(), with no compile error, but with the Obsolete warning. This means new code will typically not use the obsolete method as the developer will not see it at all. If we add a reference to the legacy assembly (which contains an extension method for the given class/interface, with the same name, in this case "VerifyAll"), here's what happens instead:

image

Note how the method becomes fully visible, but we never see the extension method version (which should have a blue down arrow). This is nice, as it means that the user sees the documentation and attributes for the non-extension method, which still lives in your core assembly.


The next version of Moq will use this to always hide renamed members from intellisense, and to make the old ones that people might still need to use visible when they add a reference to Moq.Legacy.dll. Regardless of whether you add a reference to this assembly or not, your existing test projects will not be broken and will continue to compile.

posted on Sunday, January 11, 2009 12:23 PM by kzu

# Interesting Finds: January 12, 2009 @ Monday, January 12, 2009 7:01 AM

Anonymous

# re: Evolving an API without breaking clients via extension methods, ObsoleteAttribute and EditorBrowsableAttribute: Part II @ Thursday, January 29, 2009 12:01 PM

Wow! The bug has been fixed in record time by the VS team!

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=395913

Thanks for voting!

kzu