LINQ Overview C# Help

Before getting into the features of LINQ, this section uses an example to show how queries across objects were done before LINQ was available. As you read on, the query will evolve to show how the LINQ query is reached. By going through the steps you will know what’s behind the LINQ query.

The example in this chapter is based on Formula-I world champions. Queries are done across a list of Racer objects. The first query gets all Formula-l champions from Brazil in the order of races won.

Query using List <T>

The first variant of a filter and sort is to search data in a list of type List<T>. Before the search can start, the object type and the list must be prepared.

For the object,the type Racer is defined. Racer defines several properties and an overloaded ToString () method to display a racer in a string format. This class implements the interface TFormattable to support different variants of format strings, and the interface IComparable<Racer>, which can be used to sort a list of racers based on the LastName. For doing more advanced queries, the class Racer contains not only single value properties such as FirstName, LastName, Wins, Country, and Starts, but also multivalue properties such as Cars and Years. The Years property lists all the years of the championship title. Some racers have won more than one title. The Cars property is used to list all the cars that have been used by the driver during the title years.

using System;
using System. Text;
namespace CSharp.LINQ
{
[Serializable]
public class Racer: ICornparable<Racer>, IForrnattable
{
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 string[] Cars { get; set;
public int[] Years { get; set; }
public override string ToString()
{
return String.Forrnat(‘{O} {l}’,
, FirstNarne, LastName);
public int CompareTo(Racer other)
{
return this.LastName.CompareTo(
other.LaltName); .
public Itring ToString(string format)
{
return ToString(forrnat, n 1);
public string ToString(string format,
IFormatProvider forrnatProvider)
Iswitch (format)
{
case null:
case ‘N’:
return ToString();

case ‘F’:
return FirstName;
case ‘L’:
return LastName;
case ‘C’:
return Country;
case’S’ :
return Starts.ToString();
case ‘W’:
return Wins.ToString();
case ‘A’:
return String.Format(‘{O} (lL {2};’ +
‘starts: {3}, wins: {4}’,
FirstName, LastName, Country,
Starts, Wins);
default:
throw new FormatException(String.Format(
‘Format (OJ not supported’, format));

}

}

}

}

The class Formulal returns a list of racers in the method GetChampions ().The list is filled with all Formula-1 champions from the years 1950 to 2007:

Capture

Capture1

Capture

Capture

Capture1

Capture2

Capture

For later queries where queries are done across multiple lists, the GetConstructorChampions () method that follows returns the list of all constructor championships. Constructor championships have been around since 1958.

Capture

Now let’s get into the heart of the object query. First, you need to get the list of objects with the static method GetCharnpions (). The list is filled into the generic class List<T>. The FindAll () method of this class accepts a Predicate<T> delegate that can be implemented as an anonymous method. Only the racers whose Country property is set to Brazil should be returned. Next, the resulting list is sorted with the Sort () method. The sort should not be-done by the LastName property as is the default sort implementation of the Racer class, but you can pass a delegate of type Comparison<T>.1t is again implemented as an anonymous method to compare the number of wins. Using the r2 object and comparing it with r1 does a descending sort as is required. The foreach statement finally iterates through all Racer objects in the resulting sorted collection.

Capture

The list displayed shows all champions from Brazil, sorted by the number of wins:

Ayrton Senna, Brazil; starts: 161. wins: 41
Nelson Piquet, Brazil; starts: 204, wins: 23
Emerson Fittipaldi. Brazil; starts: 143. wins: 14

In the previous sample, methods from the List<T> class, FindAll () and Sort () have been used. It would b! great to get the functionality of these methods with any collection and not just List<T>. This is where-extension methods come into play. Extension methods are new to C# 3.0. This is the first change of the previous sample that will lead toward LINQ.

Extension Methods

Extension methods make it possible to write a method to a class that doesn’t offer the method at first. You can also add a method to any class that implements a specific interface, so multiple classes can make use of the same implementation.

For example, wouldn’t you like to have a Foo () method with the String class? The String class is sealed, so it is not possible to inherit from this class. You can do an extension method, as shown:

public static class StringExtension
{
public static void Foo(this string s)
(
Console.WriteLine(“Foo invoked for {O}”, s);

}

}

An extension method is declared in a static class. An extension method is defined as a static method where the first parameter defines the type it extends. The Foo () method extends the string class, as is defined with the first parameter. For differentiating extension methods from normal static methods, the extension method also requires the this keyword with the first parameter.

Indeed, it is now possible to use the Foo () method with the string type:

string s = “Hello”;
s.Foo() ;

The result shows Foo invoked for Hello in the console, because Hello is the string passed to the Foo () method.

This might appear to be breaking object-oriented rules because a new method is defined for a type without changing the type. However, this is not the case. The extension method cannot access private members of the type it extends. Calling an extension method is just a new syntax of invoking a static method. With the string you can get the same result by calling the method Foo () this way:

string s = ‘Hello’;
StringExtension.Foo(s);

To invoke the static method, write the class name followed by the method name. Extension methods are ~a different way to invoke static methods. You don’t have to supply the name of the class where the static method is defined. Instead, the static method is taken because of the parameter type. You just have to import the namespace that contains the class to get the Foo () extension method in the scope of . the String class.

One of the classes that define LINQ extension methods is Enumerable in the namespace System. Linq. You just have to import the namespace to open the scope of the extension methods of this class. A sample implementation of the Where () extension method is shown here. The first parameter of the Where ( ) methodthat includes the this keyword is of type IEnumerable<T>. This way the Where () method can be used with every type that implements IEnumerable<T>. To mention just a few examples, arrays and List<T> implement IEnumerable<T>. The second parameter is a Func<T, bool> delegate that references a method that returns a Boolean value and requires a parameter of type T. This predicate is invoked within the implementation to examine if the item from the IEnumerable<T> source should go into the destination collection. If the method is referenced by the delegate, the yield return statement returns the item from the source to the destination.

Capture

Because Where () is implemented as a generic method, it works with any type that is contained in a collection; Any collection implementing IEnumerable<T> is supported.

The extension methods here are defined in the namespace System. Linq in the assembly System. Core.

Now it’s possible to use the extension methods Where ( ), OrderByDescending ( ) , and Select () from the class Enumerable. Because each of these methods returns IEnumerable<TSource>, it is possible to invoke one method after the other by using the previous result. With the arguments of the extension methods, anonymous methods that define the implementation for the delegate parameters are used.

private static void ExtensionMethods()
(
List<Racer> champions =
new List<Racer>(
Formulal.GetChampions() );
IEnumerable<Racer> brazilChampions
champions.Where(
delegate(Racer r)·

{

return r.Country == ‘Brazil’;
}) .OrderByDescending(
delegate(Racer r)
(
return r.Wins;
}) . Select (
delegate(Racer r)
(
return r;
}) ;
foreach (Racer r in brazilChampions)
(
Console.WriteLine(‘(O:A}’, r);

}

}

Lambda Expressions

C# 3.0 has a new syntax for anonymous methods – Lambda expressions. Instead of passing anonymous methods to the Where (), OrderByDescending (), and Select () methods, the same can be done using Lambda expressions.

Here the previous example is changed to make use of Lambda expressions. Now the syntax is shorter and also easier to understand due to the removal of the return statement, the parameter types, and the curly Brackets.

By comparing Lambda expressions to anonymous delegates you can find many similarities. To the left of the Lambda operator => are parameters. It’s ok not to add the parameter types because they are resolved by the compiler. The right side of the Lambda operator defines the implementation. With anonymous methods, curly brackets and the return statement are required. With Lambda expressions the syntax elements are not required because they are done by the compiler in any case. H you have more than one statement on the right side of the Lambda operator, curly brackets and the return statement are possible.

private static void LambdaExpressions()
. (
IEnumerable<Racer> brazilChampions =
Formulal.GetChampions().
Where{r => r.Country == ‘Brazil’).
OrderByDescending{r => r.Wins).
Select(r => r);
foreach (Racer r in brazilChampions)
(
Console.WriteLine(‘{O:A}’, r);

}

}

Return statements and curly brackets are optional when using Lambda expressions without parameter types. You can still use these language constructs with Lambda expressions.

LINQ Query

The last change that needs to be done is to define the query using the new LINQ query notation. The “‘statement from r in Formulal.GetChampions () where r. Country == ‘Brazil’ orderby r .Wins descending select r; is a LINQ query. The clauses from, where, orderby, descending, and select are predefined keywords in this query. The compiler maps these clauses to extension methods. The syntax used here is using the extension methods Where ( ), OrderByDescending ( ), and Select ( ) . Lambda expressions are passed to the parameters.

where r.Country == ‘Brazil’isconvertedtoWhere(~ => r.Country == ‘Brazil’) .orderby r.Wins descending is converted to OrderBypescending (r => r.Wins).

Capture

The LINQ query is a simplified query notation inside the C# language. The compiler compiles the query expression to invoke extension methods. The query expression is just a nice syntax from C#, but changes to the underlying IL code are not needed.

The query expression must begin with a from clause and end with a select or group clause. In between you can optionally use’where, orderby, join, let, and additional from clauses.

It is important to note that the variable query just has the LINQ query assigned to it. The query is not done by this assignment. The query is done as soon as the query is accessed using the foreach loop. This is discussed in more detail later.

With the samples so far you’ve seen new C# 3.0 language features and how they relate to the LINQ query. Now is the time to dig deeper into the features of LINQ.

Deferred Query Execution

When the query expression is defined during runtime, the query does not run. The query runs when the items are iterated.

Let’s have a look once more at the extension method Where ( ). This extension method makes use of the yield return statement to return the elements where the predicate is true. Because the yield return statement is used, the compiler creates an enumerator and returns the items as soon as they are accessed from the enumeration.

Capture

This has a very interesting and important effect. With the following example a collection of String elements is created and filled with the name arr. Next, a query is defined to get all names from the collection where the item starts with the letter J. The collection should also be sorted. The iteration does not happen when the query is defined. Instead, the iteration happens with the foreach statement, where all items are iterated. Only one element of the collection fulfills the requirements of the where expression by starting with the letter J: Juan. After the iteration is done and Juan is written to the console, four new names are added to the collection. Then the iteration is done once more.

Capture

Because the iteration does not happen when the query is.defined, but it does happen with every foreach, changes can be seen, as the output from the application demonstrates:

First iteration
Juan
Second iteration
Jack
Jim
John
Juan

Of course, you also must be aware that the-extension methods are invoked every time the query is used within an iteration. Most of the time this is very practical, because you can detect changes in the source data. However, there are situations where this is impractical. You can change this behavior by invoking the exte:r:tSionmethods ToArray (), ToEmimerable (), ToList (), and the like. In the example, you can see that ToList iterates through the collection immediately and returns a collection implementing IList-c;string>. The returned list is then iterated through twice; in between iterations, the data source gets new names.

Capture

In the result, you.can see that in between the iterations the output stays the same although the collection values changed:

First iteration
Juan
Second iteration
Juan

Posted on October 29, 2015 in Language Integrated Query

Share the Story

Back to Top