January 5th, 2007

Extension methods (part 1)

Hi.

I’m Scott Wisniewski, a dev on the VB Compiler Team. I’ve been here for a while now (about a year and a half), but this is my first blog post. I’ve been spending the past seven months or so working on Orcas, and finally decided it would be a good time to come up for some air, talk about what I’ve been working on, and start to actually make a dent against my blogging commitments.

I figured the best place to start is with my favorite feature: extension methods. If you didn’t know this already, one of the new features we are adding to VB 9.0 is Extension Methods, which provide a very powerful and elegant way to inject your own custom functionality into other people’s types. In particular, through a great deal of compiler magic, Extension methods enable you to create a method in a module decorated with an attribute and have that method appear as if it was an instance method defined on another type. Here is a simple example, where we define an extension method that adds a method called “Speak” directly to the string class.

Imports System.Runtime.CompilerServices
Imports System.Speech.Synthesis

Module Module1
   
<Extension()> _
   
Sub Speak(ByVal x As String)
       
Using synth As New SpeechSynthesizer()
           
synth.Speak(x)
       
End Using 
    
End Sub

    Sub Main()
       
Dim x = “Hello World”
       
x.Speak()
   
End Sub
End Module

This method then wraps up the speech API, causing the string to be spoken through the computer’s speakers.

If we examine the method definition, it looks more or less exactly like any run-of-the-mill helper routine, with the notable exception of it being decorated with the “System.Runtime.CompilerServices.Extension” attribute. The use of this attribute instructs the compiler to treat the method as an extension method, which in turn causes the new helper method to appear as if it was a pre-defined instance method automatically available to users of the string class.

If we compare this to the old way of doing things, the feature really starts to show its elegance. In Whidbey, helper routines that operated on common types(particularly non-extensible types like “string”, “sealed classes”, and interfaces) often suffered from a few draw backs. Mainly:

  1. They were not easily discoverable through intellisence
  2. They use an awkward, counter-intuitive syntax

Intellisence is, in my opinion, one of the greatest programming tools ever invented. Beyond the obvious productivity gains it introduces, is the tremendous discoverability it provides. Many programmers (myself included) often find themselves dealing with objects they have either never seen before or are generally unfamiliar with. In these cases, Intellisence proves to be incredibly valuable, because it allows you to effortlessly discover how these objects work. If you want to know what data is stored in an object, or what operations it is capable of performing, you simply just need to type a “.” after the object name or the operation that provided the object to you and intellisence will sweep in and provide you a nice organized picture of what the object is capable of.

With custom-written shared utility methods in Whidbey, however, this kind of discoverability is not possible. Although intellisence will provide parameter info about how to call these methods, and will provide code completion if you start to type their names, it provides little in the way of help for people who do not know that these utility methods exist and are available for their consumption. In particular, calling a utility method uses a different syntax than calling an instance method (Speak(“Hello World”) versus “Hello World”.Split()), and so while typing “.” after a string will display “Split” amongst the list of possible choices, and parameter info will be provided after typing “Speak(“, the user of an API has to know, ahead of time, weather the function he or she is calling is a shared utility method written by a third party or an instance method provided directly by the class author in order to get help from the compiler about how to use it.

Extension methods in Orcas, however, fix this problem by merging third party utility methods with canned instance methods written by class authors, essentially making utility methods just as discoverable as the built-in ones. The screen-shot below shows an example of this in action:

Extension Methods Are Cool

A programmer in Whidbey, who may not have been aware that a helper method named “Speak” existed, would receive immediate feedback in Orcas about its existence. Personally, I think this is revolutionary.

Also, instance methods have a distinct advantage over shared utility methods in that they make it much easier to chain operations together. As an example, consider a task which requires Translating a string from English to Spanish, running it through a grammar checker, and then finally sending it out as the body of an Email message. Accomplishing this using helper methods may look something like this:

SendEmailToCustomer(TranslateToSpanish(RunGrammerChecker(“Extension methods are cool”.) ) )

This has the problem, however, of being somewhat counterintuitive. The operations are specified in the opposite order in which they are executed, which makes the code both harder to write and more difficult to understand. If these helper methods were written as Extension methods, however, they could be called just like instance methods allowing users of the API to write something like this:

“Extension methods are Cool”.RunGrammarChecker().TranslateToSpanish().EmailToCustomer()

This is easier to write, because intellisence provides better completion lists, and easier to read because the operations are specified in the same order they execute.

In any case, I hope this is enough to wet your appetite for the coolness of extension methods. In my next post, I’ll dig a bit more into the details of how they work, the rules for defining them, and how they are used.

Author

0 comments