The last blog post was about LINQ grouping. Next I’m stepping into compound from. With a LINQ compound from query a list where the elements contain an inner list can be flattened.
Like the previous sample, I’m using the Racer type with the query. The properties Cars and Years can be used with a compound from.
public class Racer { public string FirstName { get; set; } public string LastName { get; set; } public int Wins { get; set; } public string Country { get; set; } public int Starts { get; set; } public int PolePositions { get; set; } public string[] Cars { get; set; } public int[] Years { get; set; } //...
This time the goal of the query is to find racers based on the car which is a collection by itself. With the list of F1 champions that is returned from Formula1.GetChampions(), every champion can be a champion with multiple cars. For example, Juan Manuel Fangio won the F1 world championship with Alfa Romeo, Maserati, Mercedes, and Ferrari.
Getting into the cars from racers can be done with a compound from. The first from is to iterate the racers, and the second one the cars from each racer.
string car = "Ferrari"; var q = from r in Formula1.GetChampions() from c in r.Cars where c == car select r;
Such a compound from is changed to invoke the SelectMany method. The where and select clauses are changed to invoke the Where and Select methods.
string car = "Ferrari"; var q = Formula1.GetChampions() .SelectMany(r => r.Cars, (r1, c1) => new {Racer = r1, Car = c1}) .Where(rc => rc.Car == car) .Select(rc => rc.Racer);
The SelectMany method has several overloads. With the LINQ Query statement after the compound from both the outer collection (the racers) and the inner collection (the cars) is accessed with following clauses. So the SelectMany overload must return racers and cars. This is possible with the overload IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource>, Func<TSource, IEnumerable<TCollection>>, Func<TSource, TCollection, TResult>).
The first parameter this IEnumerable<TSource> defines an extension method, in the sample for IEnumerable<Racer>. This is returned from Formula1.GetChampions.
The second parameter Func<TSource, IEnumerable<TCollection>> is used to flatten the inner collection. This parameter specifies a delegate where the return type is IEnumerable<TCollection>. The source is a Racer, and the return by returning the country is IEnumerable<string>. So this parameter is resolved to Func<Racer, IEnumerable<string>>. The lambda expression receives a racer with the parameter r and returns a list of cars.
The third parameter Func<TSource, TCollection, TResult> defines what should be returned from the SelectMany method. The return of the method is IEnumerable<TResult>. With the sample, this resolves to Func<Racer, string, TResult>. The first parameter is the racer and the second parameter the car. This lambda expression returns an anonymous type that contains a Racer and a Car property.
The Where method uses the Car property of the anonymous type for filtering, and the Select method uses the Racer property to return all racers where the filter applies.
Instead of iterating the query directly next I’m defining a variable of type Func<string, IEnumerable<Racer>> to assign a lambda expression that contains the query. This lambda expression that’s referenced from the delegate can be invoked by passing a car as parameter. This way it’s easy to invoke methods such as Union or Intersect.
Func<string, IEnumerable<Racer>> racerByCar = car => from r in Formula1.GetChampions() from c in r.Cars where c == car select r; foreach (var r in racerByCar("Ferrari").Intersect(racerByCar("McLaren"))) { Console.WriteLine(r); }
The Visual Basic version of the above looks like this:
Dim racerByCar = Function(car As String) Return From r In Formula1.GetChampions() From c In r.Cars Where c = car Select r End Function For Each r In racerByCar("Ferrari").Intersect(racerByCar("McLaren")) Console.WriteLine(r) Next
If you would like to know the results of all F1 champions that have won the championship both with Ferrari and McLaren: Niki Lauda.
More LINQ posts:
- LINQ grouping
- LINQ with .NET 4 – ZIP
- LINQ with Joins
- Custom extension methods
- What’s behind a query expression
- Filtering and sorting XML
- Filtering and sorting data from the database
- Filtering and sorting object lists
More about LINQ with C# in my book Professional C# 4 with .NET 4, and with the language of your choice in my LINQ programming workshop. There’s also more about LINQ in the upcoming BASTA! On Tour in Munich.
Christian
Comments