Implement interface in Whidbey

Shaykat, another PM on the C# team, recently posted (https://blogs.msdn.com/shaykatc/archive/2004/03/10/87582.aspx) a VS 2003 tip for automatically implementing an interface. We have received almost universally positive feedback about it, but there were definitely some gotchas:

  • It works quite well for implicitly implementing an interface for the first time, but it's not reentrant with an easy interaction (you almost always have to delete and re-type the interface name), such that if you add additional members to an interface it's hard to use the feature to implement stubs for those.
  • In 2003 we attempted to intelligently choose whether to implement the interface implicitly or explicitly. If there were existing members with the same name but a different signature then we would implement that member explicitly. Unfortunately we weren't entirely consistent so you'd occasionally end up with code that wouldn't build. That type of implicit vs. explicit inference also turned out to be a bug farm.
  • The code spit that we chose for generating the stubs is sub-optimal. It would determine the return type and return either null if it was a reference type, or a default value for a value type (if it was a known value type like int, then it would return 0; if it was unknown then it would return “new ValueTypeName();“). Unfortunately this means that you can write code against these interface members and have the code 'work', even though there is no backing implementation. This is occasionally useful, but we've had lots of requests to throw a NotImplementedException instead.
  • There was no way to modify the code spit. You couldn't add or remove TODO comments, for example.

We have attempted to tackle all of these issues in Whidbey. In order to invoke this feature in Whidbey you'll use an editor 'smart tag'. This tag is a little marker that appears underneath an interface name when the cursor is on the same line in which it appears. It can be invoked through the keyboard or by hovering the mouse over the marker. After invocation a context like menu will appear, which in this case, will offer two options. To either implement the interface implicitly, or explicitly. 

Shows the highlighted marker under IComparable
Shows the expanded menu giving a choice between implicit and explicit implementations

You've probably already determined how this addressees the first two issues. First, the smart tag is always available, so if you add additional members to an interface then it's easy to invoke the feature again and generate the additional stubs. Second, we no longer attempt to determine whether to implement the interface explicitly or implicitly, instead we leave that decision in the hands of the developer (where it belongs). 

The code generation bullets are interesting, because there are a lot of features in Whidbey that generate code in some manner. Given that, we decided to leverage our expansions feature (I'll blog about this separately if you haven't read about it elsewhere yet) to allow developers to modify the generated code. For example, the implement interface feature uses an expansion that looks like this:

<Declarations>
<Literal>
<ID>signature</ID>
<Default>signature</Default>
</Literal>
<Literal>
<ID>NotImplementedException</ID>
<Function>SimpleTypeName(global::System.NotImplementedException)</Function>
</Literal>
</Declarations>
<Code Language="CSharp" Format="CData">
<![CDATA[
$signature$
{
throw new $NotImplementedException$();
}]]>
</Code>

The $signature$ literal is replaced when the code is generated. To put a comment in every generated method stub, you would just add it to this file. The $NotImplementedException$ literal is worth mentioning as well. Its replacement value is determined by a function. In this case, the function “SimpleTypeName” is used, which determines what the 'simplest' form of global::System.NotImplementedException is. That is, if you were to implement an interface in a context with no using directives, it would insert System.NotImplementedException; however, if you were to implement the same interface in a scope in which the System namespace had been imported then it would use 'throw NotImplementedException()'.