After lots of procrastination, I finally decided I was being left behind by the .Net community and started researching LINQ (Language INtegrated Query) and its underlying technologies - anonymous methods, anonymous types, lambda expressions (predicates and assignments), and extender methods etc. Ironically, I purchased an APress’ Pro LINQ exactly one day before finding out that Microsoft had made Introduction to Microsoft LINQ freely available (or nearly so - you need a Passport account, and have to aquiesce to a short personal info questionnaire). I don’t feel like my APress dollars were wasted, however. Their treatment of the different classes of LINQ (-to Objects, -to ADO.Net, -to XML, etc.) appears to be fairly exhaustive, where the ‘Intro’ volume looks like it lays the groundwork for the technology. One of the items that caught my eye immediately was the concept of extender methods, which allow developers to change the behavior of existing types - even (or perhaps particularly) sealed ones. Now, that’s an excellent idea. For instance, if I wanted to fix things so that the System.String type implemented a ToPalindrome() method, I could do so using an extender method like so:
001public static class StringExtender
002{
003 public static string ToPalindrome(this string value)
004 {
005 if (string.IsNullOrEmpty(value) == false)
006 return value + value.Reverse();
007 return null;
008 }
009 public static string Reverse(this string value)
010 {
011 if (string.IsNullOrEmpty(value) == false)
012 {
013 char[] cArray = value.ToCharArray();
014 Array.Reverse(cArray);
015 return new string(cArray);
016 }
017 return null;
018 }
019}
…and yes, I know this is a very contrived example, but it still underscores a very cool new feature. After looking at the extender methods concept for a little while, I started to wonder what would happen in the case of virtual methods where there was ambiguity between the virtual method and an extender with the same signature. As most .Net developers should (hopefully do) know, virtual members are (almost) always correctly resolved by the runtime, and their resolution certainly always adheres to specific rules. Most developers I know simply cite the "It is what it *IS*" rule, meaning that, no matter how an instance is declared, the type used to instantiate it is the one used to determine its behavior.Example: Assuming we have types A and B, and type B inherits from type A, and further assuming there is a virtual method on A called DoSomething that is overridden on B, then the following code would result in B's version of the method being executed every time...
A bInstance = new B();
bInstance.DoSomething(); // Executes B.DoSomething()
The only time we don't see this kind of behavior is when the method being replaced is not virtual, or the overrides keyword is not used. The resulting code will compile, but the behavior is different, and can be unexpected and confusing. The behavior is called 'shadowing' or 'hiding' and usually yields a compiler warning. However, when methods are 'hidden' in this way, the path to the appropriate virtual member cannot be determined at run-time, so the declared type makes a difference. What that means is that in the example above, if B.DoSomething() had hidden A.DoSomething(), the call to bInstance.DoSomething() would have executed A.DoSomething() instead.Confusing enough? The only reason I mention this is that the treatment of extender methods is handled in much the same way as shadowing or hiding, just in a more institutionalized manner. The rules are:
- Instance methods are always evaluated first.
- Extender methods are evaluated second.
- Virtual methods are evaluated last.
What the list above means, although it may not be obvious, is that the declared type (the "left side type name" of a variable declaration) of a variable matters where extender methods are concerned. Why? Because virtual members are resolved at run-time, but extender methods are linked at compile-time. Essentially, if there is an available extender method on the declared type, then any virtual version of the same method will be totally ignored at run-time - in fact, it won't even exist as a possibility in the byte code. The following example code, more or less a copy of a similar example in the Introduction to Microsoft LINQ volume, illustrates the effect, which I find very similar to the previous description of shadowing/hiding members:
001public class ParentType
002{
003 public virtual void X() { }
004}
005public class ChildType : ParentType
006{
007 public override void X() { }
008 public void Y() { }
009}
010public static class Extender
011{
012 static void X(this ParentType a) { }
013 static void Y(this ParentType b) { }
014 public static void TryItOut()
015 {
016 ParentType a = new ParentType();
017 ChildType b = new ChildType();
018 ParentType c = new ChildType();
019 a.X(); // Calls ParentType.X()
020 b.X(); // Calls ChildType.X()
021 c.X(); // Calls ChildType.X()
022 a.Y(); // Calls Extender.Y()
023 b.Y(); // Calls ChildType.Y()
024 c.Y(); // Calls Extender.Y()
025 }
026}
Now, that's about enough out of me for one day...