Monday, April 30, 2007 11:29 AM bart

Answers to C# 3.0 Quiz - Core LINQ concepts

This post covers the answers to the C# 3.0 - Core Linq concepts questions. Alex and Ming provided a set of correct answers, so there's not much I can say (that's what quizzes are for after all) :-). Nevertheless, here's my story...

Let's refresh the questions first:

A piece of C# 3.0 code - Copy Code
1 // 2 // Put additional namespace imports here 3 // 4 5 class Program 6 { 7 static void Main() 8 { 9 Source<Customer> src = null; 10 var res = from s in src 11 orderby s.Name, s.Age descending 12 where s.Name == "Bart" 13 select s.Age; 14 } 15 } 16 17 class Customer 18 { 19 public string Name { get; set; } 20 public int Age { get; set; } 21 } 22 23 abstract class Source<T> 24 { 25 // 26 // Put code here 27 // 28 }

The goal is to extend the Source<T> class in order to make the query in Program::Main work "properly" (in a sense it does compile without errors). Notice we can't change line 23 to implement IQueryable<T> or so. Basically, what the compiler does as it encounters the query in line 10 to 13 is to rewrite it to the following:

Copy Code
1 var res = src.OrderBy(s => s.Name) 2 .ThenByDescending(s => s.Age) 3 .Where(s => s.Name == "Bart") 4 .Select(s => s.Age);

These => things are called lambda expressions and are simply a shorthand way to write anonymous delegates. The whole point about this model is that the compiler will try to resolve the method calls to OrderBy, ThenByDescending, Where and Select with the appropriate signature. It does so by starting to look for instance members (of course starting with OrderBy since subsequent calls depend on that method's return type). If it doesn't find instance members, it will start to look for extension methods that are brought in scope with a using statement. If we'd implement IEnumerable<T> from System.Linq on line 23, and added a using System.Linq statement to lines 1-3, we'd be using LINQ-to-Objects which has an implementation for all of these methods. In this case, we don't have such an implementation and we'll have to implement the methods on our own. Here are the signatures:

Copy Code
1 abstract class Source<T> 2 { 3 public abstract Source<T> OrderBy(Func<T, string> keySelector); 4 public abstract Source<T> ThenByDescending(Func<T, int> keySelector); 5 public abstract Source<T> Where(Func<T, bool> predicate); 6 public abstract Source<T> Select(Func<T, int> selector); 7 }

To make this work, you'll need to import System.Linq in order to have the Func* generic delegates in scope. For the generic parameter types, you could think of alternatives but we won't consider these. This implementation will cause the compiler to emit anonymous methods for the various method parameter delegates, i.e.:

Copy Code
1 var res = src.OrderBy(delegate (Customer s) { return s.Name; }) 2 .ThenByDescending(delegate (Customer s) { return s.Age; }) 3 .Where(delegate (Customer s) { return s.Name == "Bart"; }) 4 .Select(delegate (Customer s) { return s.Age; });

However, an alternative exists which won't translate into IL code directly: enter expression trees. The signatures this time look like this:

Copy Code
1 abstract class Source<T> 2 { 3 public abstract Source<T> OrderBy(Expression<Func<T, string>> keySelector); 4 public abstract Source<T> ThenByDescending(Expression<Func<T, int>> keySelector); 5 public abstract Source<T> Where(Expression<Func<T, bool>> predicate); 6 public abstract Source<T> Select(Expression<Func<T, int>> selector); 7 }

You'll need to import System.Linq.Expressions to make this work. This time, the compiler finds the appropriate instance methods in Source<T> but with Expression<TT> parameters, which results in the generation of expression tree code. This code would drive use too far from home, but feel free to inspect it via ILDASM. Basically, it allows the implementation of Source<T> to parse the expression tree at runtime to translate it into some kind of domain specify query language (e.g. SQL, LDAP, CAML). To implement a more flexible custom query provider, you can implement IQueryable<T> as explained in other posts.

Enjoy! | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,


No Comments