: C# 2008 Programmer

LINQ and Extension Methods

LINQ and Extension Methods

Chapter 4 explored extension methods and how you can use them to extend functionality to an existing class without needing to subclass it. One of the main reasons why the extension method feature was incorporated into the C# 3.0 language was because of LINQ.

Consider the earlier example where you have an array called allNames containing an array of strings. In .NET, objects that contain a collection of objects must implement the IEnumerable interface, so the allNames variable implicitly implements the IEnumerable interface, which only exposes one method GetEnumerator. But when you use IntelliSense in Visual Studio 2008 to view the list of methods available in the allNames object, you see a list of additional methods, such as Select, Take, TakeWhile, Where, and so on (see Figure 14-2).


Figure 14-2

In C# 3.0, all these additional methods are known as extension methods, and they are extended to objects that implement the IEnumerable interface. These extension methods are the LINQ standard query operators.

In Visual Studio 2008, all extension methods are denoted by an additional arrow icon, as shown in Figure 14-3.


Figure 14-3

To add extension methods to objects implementing the IEnumerable interface, you need a reference to System.Core.dll and import the namespace by specifying the namespace:

using System.Linq;

The following table lists the LINQ standard query operators.

Operator Type Operator Name
Aggregation Aggregate, Average, Count, LongCount, Max, Min, Sum
Conversion Cast, OfType, ToArray, ToDictionary, ToList, ToLookup, ToSequence
Element DefaultIfEmpty, ElementAt, ElementAtOrDefault, First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault
Equality EqualAll
Generation Empty, Range, Repeat
Grouping GroupBy
Joining GroupJoin, Join
Ordering OrderBy, ThenBy, OrderByDescending, ThenByDescending, Reverse
Partitioning Skip, SkipWhile, Take, TakeWhile
Quantifiers All, Any, Contains
Restriction Where
Selection Select, SelectMany
Set Concat, Distinct, Except, Intersect, Union

Deferred Query Execution

The query variable itself only stores the query; it does not execute the query or store the result.

Take another look at the preceding example:

int[] nums = {
12, 34, 10, 3, 45, 6, 90, 22, 87, 49, 13, 32
};
var oddNums = nums.Where
(n => n % 2 == 1).OrderByDescending(n => n);

The oddNums variable simply stores the query (not the result of the query). The query is only executed when you iterate over the query variable, like this:

foreach (int n in oddNums)
Console.WriteLine(n);

This concept is known as deferred execution, and it means that every time you access the query variable, the query is executed again. This is useful because you can just create one query and every time you execute it you will always get the most recent result.

To prove that deferred execution really works, the following program first defines a query and then prints out the result using a foreach loop. Twenty is added to each element in the array, and then the foreach loop is executed again.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication5 {
class Program {
static void Main(string[] args) {
int[] nums = {
12, 34, 10, 3, 45, 6, 90, 22, 87, 49, 13, 32
};
var oddNums =
nums.Where(n => n % 2 == 1).OrderByDescending(n => n);
Console.WriteLine("First execution");
Console.WriteLine("---------------");
foreach (int n in oddNums) Console.WriteLine(n);
//---add 20 to each number in the array---
for (int i = 0; i < 11; i++) nums[i] += 20;
Console.WriteLine("Second execution");
Console.WriteLine("----------------");
foreach (int n in oddNums) Console.WriteLine(n);
Console.ReadLine();
}
}
}

The program prints out the following output:

First execution
---------------
87
49
45
13
3
Second execution
107
69
65
33
23

Because the output for the second foreach loop is different from the first, the program effectively proves that the query is not executed until it is accessed.

Deferred execution works regardless of whether you are using the query or method syntax.

Forced Immediate Query Execution

One way to force an immediate execution of the query is to explicitly convert the query result into a List object. For example, the following query converts the result to a List object:

var oddNums = nums.Where
(n => n % 2 == 1).OrderByDescending(n => n).ToList();

In this case, the query is executed immediately, as proven by the following program and its output: using System;

using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication5 {
class Program {
static void Main(string[] args) {
int[] nums = {
12, 34, 10, 3, 45, 6, 90, 22, 87, 49, 13, 32
};
var oddNums = nums.Where
(n => n % 2 == 1).OrderByDescending(n => n).ToList();
Console.WriteLine("First execution");
Console.WriteLine("---------------");
foreach (int n in oddNums) Console.WriteLine(n);
//---add 20 to each number in the array---
for (int i = 0; i < 11; i++) nums[i] += 20;
Console.WriteLine("Second execution");
Console.WriteLine("----------------");
foreach (int n in oddNums) Console.WriteLine(n);
Console.ReadLine();
}
}
}

Here's the program's output:

First execution
---------------
87
49
45
13
3
Second execution
87
49
45
13
3

The output of the first and second execution is the same, proving that the query is executed immediately after it's defined.

To force a LINQ query to execute immediately, you can use aggregate functions so that the query must iterate over the elements at once. An aggregate function takes a collection of values and returns a scalar value.

Aggregate functions are discussed in more detail later in this chapter.

Following is an example that uses the Count() aggregate function. The program selects all the odd numbers from an array and then counts the total number of odd numbers. Each number is then multiplied by two (which makes them all become even numbers).

static void Main(string[] args) {
int[] nums = {
12, 34, 10, 3, 45, 6, 90, 22, 87, 49, 13, 32
};
var oddNumsCount = nums.Where
(n => n % 2 == 1).OrderByDescending(n => n).Count();
Console.WriteLine("First execution");
Console.WriteLine("---------------");
Console.WriteLine("Count: {0}", oddNumsCount);
//---add 20 to each number in the array---
for (int i = 0; i < 11; i++)
nums[i] *= 2; //---all number should now be even---
Console.WriteLine("Second execution");
Console.WriteLine("----------------");
Console.WriteLine("Count: {0}", oddNumsCount);
Console.ReadLine();
}

The output shows that once the query is executed, its value does not change:

First execution
---------------
Count: 5
Second execution
----------------
Count: 5


: 1.164. /Cache: 3 / 1