In Chapter 9, we introduced LINQ, demonstrated LINQ query syntax and introduced some LINQ extension methods. The same tasks you can perform with LINQ query syntax can also be performed with various LINQ extension methods and lambdas. In fact, the compiler translates LINQ query syntax into calls to LINQ extension methods that receive lambdas as arguments. For example, lines 21–24 of Fig. 9.2
var filtered =
from value in values // data source is values
where value > 4
select value;
can be written as
var filtered = values.Where(value => value > 4);
Figure 21.8 demonstrates simple functional programming techniques using a list of integers.
Display
Throughout this example, we display the results of various operations by calling our own extension method named Display
, which is defined in the static class Extensions
(lines 57–64). The method uses string
method Join
to concatenate the IEnumerable<T>
argument’s elements separated by spaces.
Note at the beginning and end of Main
that when we call Display
directly on the values
collection (lines 16 and 52) the same values are displayed in the same order. These outputs confirm that the functional-programming operations performed throughout Main
(which we discuss in Sections 21.11.1–21.11.4) do not modify the contents of the original values
collection.
Min
, Max
, Sum
and Average
Class Enumerable
(namespace System.Linq
) defines various LINQ extension methods for performing common reduction operations including:
Min
(line 19) returns the smallest value in the collection.
Max
(line 20) returns the largest value in the collection.
Sum
(line 21) returns the sum of all the values in the collection.
Average
(line 22) returns the average of all the values in the collection.
Note in lines 19–22 that for each of these reduction operations:
We simply say what we want to accomplish, not how to accomplish it—there are no iteration details in the app.
No mutable variables are used in the app to perform these operations.
The values
collection is not modified (confirmed by the output of line 52).
In fact, the LINQ operations have no side effects that modify the original collection or any other variables in the app—a key aspect of functional programming.
Of course, behind the scenes iteration and mutable variables are required:
All four extension methods iterate through the collection and must keep track of the current element they’re processing.
While iterating through the collection, Min
and Max
must store the current smallest and largest items, respectively, and Sum
and Average
must keep track of the total of the elements processed so far—all of these require mutating a local variable that’s hidden from you.
The other operations in Sections 21.11.2–21.11.4 also require iteration and mutable variables, but the library—which has already been thoroughly debugged and tested—handles these details for you. To see how LINQ extension methods like Min
, Max
, Sum
and Average
implement these concepts, check out class Enumerable
in the .NET source code at
https://github.com/dotnet/corefx/tree/master/src/System.Linq/src/
System/Linq
Class Enumerable
is divided into many partial
classes—you can find methods Min
, Max
, Sum
and Average
in the files Min.cs
, Max.cs
, Sum.cs
and Average.cs
.
Aggregate
Extension Method for Reduction OperationsYou can define your own reductions with the Aggregate
LINQ extension method. For example, the call to Aggregate
in lines 25–26 sums the elements of values
. The version of Aggregate
used here receives two arguments:
The first argument (0
) is a value that helps you begin the reduction operation. When summing the elements of a collection, we begin with the value 0
. Shortly, we’ll use 1 to begin calculating the product of the elements.
The second argument is a delegate of type Func
(namespace System
) that represents a method which receives two arguments of the same type and returns a value— there are many versions of type Func
that specify from 0 to 16 arguments of any type. In this case, we pass the following lambda expression, which returns the sum of its two arguments:
(x, y) => x + y
Once for each element in the collection, Aggregate
calls this lambda expression.
On the first call to the lambda, parameter x
’s value is Aggregate
’s first argument (0
) and parameter y
’s value is the first int
in values
(3
), producing the value 3
.
Each subsequent call to the lambda uses the result of the previous call as the lambda’s first argument and the next element of the collection as the second. On the second call to the lambda, parameter x
’s value is the result of the first calculation (3
) and parameter y
’s value is the second int
in values
(10
), producing the sum 13
.
On the third call to the lambda, parameter x
’s value is the result of the previous calculation (13
) and parameter y
’s value is the third int
in values
(6
), producing the sum 19
.
This process continues producing a running total of the values until they’ve all been used, at which point the final sum is returned by Aggregate
. Note again that no mutable variables are used to reduce the collection to the sum of its elements and that the original values
collection is not modified.
Aggregate
Lines 29–30 use Aggregate
to calculate the sums of the squares of values
’ elements. The lambda in this case,
(x, y) => x + y * y
adds the square of the current value to the running total. Evaluation of the reduction proceeds as follows:
On the first call to the lambda, parameter x
’s value is Aggregate
’s first argument (0
) and parameter y
’s value is the first int
in values
(3
), producing the value 9
.
On the next call to the lambda, parameter x
’s value is the result of the first calculation (9
) and parameter y
’s value is the second int
in values
(10
), producing the sum 109
.
On the next call to the lambda, parameter x
’s value is the result of the previous calculation (109
) and parameter y
’s value is the third int
in values
(6
), producing the sum 145
.
This process continues producing a running total of the squares of the elements until they’ve all been used, at which point the final sum is returned by Aggregate
. Note again that no mutable variables are used to reduce the collection to the sum of its squares and that the original values
collection is not modified.
Aggregate
Lines 33–34 use Aggregate
to calculate the product of values
’ elements. The lambda
(x, y) => x * y
multiplies its two arguments. Because we’re producing a product, we begin with the value 1
in this case. Evaluation of the reduction proceeds as follows:
On the first call to the lambda, parameter x
’s value is Aggregate
’s first argument (1
) and parameter y
’s value is the first int
in values
(3
), producing the value 3
.
On the next call to the lambda, parameter x
’s value is the result of the first calculation (3
) and parameter y
’s value is the second int
in values
(10
), producing the sum 30
.
On the next call to the lambda, parameter x
’s value is the result of the previous calculation (30
) and parameter y
’s value is the third int
in values
(6
), producing the sum 180
.
This process continues producing a running product of the elements until they’ve all been used, at which point the final product is returned. Note again that no mutable variables are used to reduce the collection to the product of its elements and that the original values
collection is not modified.
Where
Extension Method for Filtering OperationsLines 38–40 filter the even integers in values
, sort them in ascending order and display the results. You filter elements to produce a new collection of results that match a condition known as a predicate. LINQ extension method Where
(line 38) receives as its argument a Func
delegate for a method that receives one argument and returns a bool
indicating whether a given element should be included in the collection returned by Where
.
The lambda in line 38:
value => value % 2 == 0
receives a value and returns a bool
indicating whether the value
satisfies the predicate—in this case, if the value it receives is divisible by 2.
The OrderBy
extension method receives as its argument a Func
delegate representing a method that receives one parameter and returns a value that’s used to order the results. In this case (line 39), the lambda expression
value => value
simply returns its argument, which OrderBy
uses to sort the values in ascending order—for descending order you’d use OrderByDescending
. Note again that no mutable variables are used to filter or sort the collection and that the original values
collection is not modified.
Calls to Where
and OrderBy
use the same deferred execution we discussed in Section 9.5.2— they aren’t evaluated until you iterate over the results. In lines 38–40, this occurs when our Display
extension method is called (line 40). This means you can save the operation into a variable for future execution, as in
var evenIntegers =
values.Where(value => value % 2 == 0) // find even integers
.OrderBy(value => value); // sort remaining values
You can execute the operation by iterating over evenIntegers
later. Each time you execute the operation, the current elements in values
will be filtered and sorted. So, if you modify values
by adding more even integers to the collection, these will appear in the results when you iterate over evenIntegers
.
Select
Extension Method for Mapping OperationsLines 45–48 filter the odd integers in values
, multiply each odd integer by 10, sort the values in ascending order and display the results. The new feature here is the mapping operation that takes each value and multiplies it by 10. Mapping transforms a collection’s elements to new values, which sometimes are of different types from the original elements.
LINQ extension method Select
receives as its argument a Func
delegate for a method that receives one argument and maps it to a new value (possibly of another type) that’s included in the collection returned by Select
. The lambda in line 46:
value => value * 10
multiplies its value
argument by 10
, thus mapping it to a new value. Line 47 sorts the results. Calls to Select
are deferred until you iterate over the results—in this case, when our Display
extension method is called (line 48). Note again that no mutable variables are used to map the collection’s elements and that the original values
collection is not modified.