Section 4.5. Extension Methods


4.5. Extension Methods

So how exactly are query expressions executed? Query expressions are translated into traditional object-oriented method calls by way of extension methods. Extension methods, new in C# 3.0, extend a class without actually being members of that class. For example, consider the following query expression:

 using System.Query; // initials of all doctors living in Chicago, in no particular order: var chicago = from d in doctors               where d.City == "Chicago"               select d.Initials; 

Using extension methods, this query can be rewritten as follows:

 using System.Query; var chicago = doctors.               Where(d => d.City == "Chicago").               Select(d => d.Initials); 

This statement calls two methods, Where and Select, passing them lambda expressionsyet these methods are not members of the Doctors class. How and why does this compile?

In C# 3.0, extension methods are defined as static methods in a static class, annotated with the System.Runtime.CompilerServices.Extension attribute. By importing the namespace that includes this static class, the compiler treats the extension methods as if they were instance methods. For example, the LINQ standard query operators are defined as extension methods in the class System.Query.Sequence. By importing the namespace System.Query, we gain access to the query operators as if they were members of the Doctors class.

Let's look at an extension method in more detail. Here's the signature for System.Query.Sequence.Where:

 namespace System.Query {        public delegate ReturnT Func<ArgT, ReturnT>(ArgT arg);        public static class Sequence        {               public static IEnumerable<T> Where<T>(this IEnumerable<T> source,                                                     Func<T, bool> predicate)               { ... }               .               .               .        } } 

The extension method Where returns an IEnumerable object by iterating across an existing IEnumerable object (source), applying a delegate-based Boolean function (predicate) to determine membership in the result set. Notice that both the first parameter and the return value are defined in terms of IEnumerable<T>, establishing the link between query expressions and the extension methods that drive them.

Given a query expression or a query written using extension methods, the C# 3.0 compiler translates these into calls to the underlying static methods. For example, either form of our query above is conceptually translated into the following:

 var temp    = System.Query.Sequence.Where(doctors, d => d.City == "Chicago"); var chicago = System.Query.Sequence.Select(temp, d => d.Initials); 

In reality, this form is skipped and the translation proceeds directly to C# 2.0-compatible code:

 IEnumerable<Doctor> temp;     // doctors living in chicago IEnumerable<string> chicago;  // initials of doctors living in chicago temp    = System.Query.Sequence.Where<Doctor>( doctors,             new Func<Doctor, bool>(b__0) ); chicago = System.Query.Sequence.Select<Doctor, string>( temp,             new Func<Doctor, string>(b__3) ); 

with the lambda expressions translated into delegate-invoked methods:

 private static bool b__0(Doctor d)    // lambda: d => d.City == "Chicago" { return d.City == "Chicago"; } private static string b__3(Doctor d)  // lambda: d => d.Initials { return d.Initials; } 

Notice the important role that type inference plays during this translation. Extension methods and their supporting types (IEnumerable, Func, etc.) are elegantly defined once using generics. Type inference is relied on to determine the type T involved in each aspect of the query, and to then qualify the generic appropriately. In this case we see the inference of both Doctor and string.

In C# 3.0, what differentiates an extension method from an ordinary static method? Observe that a query based on extension methods is converted to a static version by passing the object instance as the first argument:

 doctors.Where(...) ==> System.Query.Sequence.Where(doctors, ...) 

This is identical to the standard mechanism for calling instance methods, where the first parameter is a reference to the object itselfproviding a value for this. This logic explains C#'s choice of the this keyword in the signature of extension methods:

 public static IEnumerable<T> Where<T>(this IEnumerable<T> source, ... ) 

In C# 3.0 it is the presence of the this keyword on the first parameter that identifies a static method as an extension method.

What if the class (such as Doctors) or one of its base classes contain methods that conflict with imported extension methods? To avoid unexpected behavior, all other methods take priorityextension methods have the lowest precedence when the compiler performs name resolution, and thus become candidates only after all other possibilities have been exhausted. If two or more imported namespaces yield candidate extension methods, the compiler reports the conflict as a compilation error.



LINQ[c] The Future of Data Access in C# 3. 0
LINQ[c] The Future of Data Access in C# 3. 0
ISBN: N/A
EAN: N/A
Year: 2006
Pages: 25

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net