Extension methods on VSM
Thu, May 31 2007 1:34
My article about Extension methods is now on the web. Unfortunately an edit I made didn't make it into the print version (and the web version is the same as the print). The problem was as I looked at the way extension methods worked in Beta 1, it was as I was told it would work form discussions I had had with members of the VB team. But as I looked at it further it dawned on me that the design of having any instance method hiding all extension methods with the same name wasn't really desirable. So I talked to some folks on the VB team about this. Meanwhile we were pushing the deadline out for print. I ended up making edits but they didn't make it into the printed release.
The last two paragraphs of the article read:
If you recall the rules around precedence, you might be wondering if methods in your collection classes might Shadow LINQ statements by mistake. The answer to that is actually rather complex. The compiler goes out of its way to let the LINQ statements work. If you add a Select method to your collection class, VB uses AsQueriable to cast your collection to an IQueriable type, which means the compile precedence is on the IQueriable type. If you also have an AsQueriable type, it tries an AsIEnumerable extension, and failing that, it tries the Cast extension method. If all of those are Shadowed then the compiler throws a compile time error.
The goal is to allow you to use LINQ statements on existing collections of all sorts. Methods inside the collection classes are viewed as being from prior to LINQ and hence having a different intent. For this reason, the compiler attempts to navigate around them. If you need to change the LINQ behavior, you must use extension methods defined in a module and implement namespace precedence to suit your needs.
That was technically correct at the time, but I believe inaccurate as to what should happen, and as to what happens in C#. The edited article replaced those two paragraphs with the following:
If you recall the rules around precedence, you might be wondering if methods in your collection classes might Shadow LINQ statements. The answer to that is instance methods will shadow by name and signature any extension methods. In the March CTP VB behaves differently: See sidebar "Out of the Shadows".
Shadowing by name and signature in this case means methods that would resolve to the same if both were instance methods. Remember extensions are Shared methods with the first parameter the instance: hence when determining if a method is shadowed by an instance method, the first parameter in the extension method is removed from the actual signature.
For example, a Select extension would be shadowed by an instance method as long as the instance method has the correct and matching signature.
' inside a module
Public Function [Select](Of TSource, TResult) _
(ByVal source As IEnumerable(Of TSource), _
ByVal selector As Func(Of TSource, TResult)) _
As IEnumerable(Of TResult)
' instance method inside the collection class
Public Function [Select](Of TResult)_
(ByVal selector As Func(Of T, TResult)) _
As IEnumerable(Of TResult)
The source parameter is removed from the signature for the extension method, generic parameters are resolved, and return types ignored to determine if the instance method shadows the extension: if they match, the instance method gets precedence.
The difficulty in using instance methods over extension methods for LINQ queries is you need to chain return types with types you have your instance methods in. Taking the earlier example of selecting a distinct list, the Distinct method has to be inside the type returned by the Select method. In that particular example you can have the Select return the same collection class and put your Distinct method in there. This becomes more complex when dealing with other projections and anonymous types. Rather than use instance methods you'll find it generally easier to import a module with your extension methods in it.
Extension methods can make creating customized LINQ queries, easier, and easier to share amongst your different libraries or collection classes. Powerful, yet incredibly simple in essence: used wisely they'll make your coding easier.
The sidebar was :
Out of the Shadows
In the March Orcas CTP, and most likely for Beta 1 of Orcas, VB shadows all extension methods by an instance method with the same name. That is, it shadows by name, not by name and signature. This has some unfortunate side effects such as the inability to add extensions with more or different parameters. A more complex issue arises around LINQ queries: if you have a Select method in your collection class that is the incorrect signature for the LINQ query, any Select extension in scope will be ignored for your collection class because they are shadowed. The compiler will then call a different extension method AsQueriable to cast the type to an IQueriable(Of T). Extension methods are then resolved for an IQueriable(Of t) rather than your collection class type. If your Select extension was written strongly typed to your collection class, it won't get called.
The good news is I've had a chat to some of the VB team, and they've promised me they'll look into. Most likely they will change it to shadows by name and signature. The bad news this change won't make it into Beta 1, you'll have to wait till Beta 2.
This highlights why we need to look at the early CTP's and betas and give Microsoft feedback. They share these early bits with us so as we can shine light on things others might not notice and give them feedback early enough to steer them clear of any rocks lurking in the shadows. J
And a special thanks to Adrian and Amanda for listening J
My sincerest apologies for not getting this in the article on time to make the print release.