2. When the app executes, another compiler (known as the just-in-time compiler
9.3 Querying an Array of Employee Objects Using LINQ
IEnumerable<T>is aninterface. Interfaces define and standardize the ways in which people and systems can interact with one another. For example, the controls on a radio serve as aninterfacebetween radio users and the radio’s internal components. The controls allow users to perform a limited set ofoperations(e.g., changing the station, adjusting the volume, and choosing between AM and FM), and different radios may implement the controls in different ways (e.g., using push buttons, dials or voice commands). The inter- face specifieswhatoperations a radio permits users to perform but does not specifyhow the operations are implemented. Similarly, the interface between a driver and a car with a manual transmission includes the steering wheel, the gear shift, the clutch, the gas pedal and the brake pedal. This same interface is found in nearly all manual-transmission cars, enabling someone who knows how to drive one manual-transmission car to drive another.
Software objects also communicate via interfaces. A C# interface describes a set of members that can be called on an object—to tell the object, for example, to perform some task or return some piece of information. TheIEnumerable<T> interface describes the functionality of any object that can be iterated over and thus offers members to access each element. A class that implements an interface must define each member in the interface with a signature identical to the one in the interface definition. Implementing an interface is like signing a contract with the compiler that states, “I will declareallthe members spec- ified by the interface.” Chapter 12 covers use of interfaces in more detail, as well as how to define your own interfaces.
Arrays areIEnumerable<T>objects, so aforeachstatement can iterate over an array’s elements. Similarly, each LINQ query returns anIEnumerable<T>object. Therefore, you can use aforeachstatement to iterate over the results of any LINQ query. The notation
<T>indicates that the interface is agenericinterface that can be used with any type of data (for example,ints,strings orEmployees). You’ll learn more about the<T>notation in Section 9.4. You’ll learn more about interfaces in Section 12.7.
9.3 Querying an Array of Employee Objects Using LINQ
LINQ is not limited to querying arrays of simple types such asints. It can be used with most data types, includingstrings and user-defined classes. It cannot be used when a que- ry does not have a defined meaning—for example, you cannot useorderbyon objects that are notcomparable. Comparable types in .NET are those that implement theIComparable
interface, which is discussed in Section 20.4. All built-in types, such asstring,intand
doubleimplementIComparable. Figure 9.3 presents theEmployeeclass. Figure 9.4 uses LINQ to query an array ofEmployeeobjects.
1 // Fig. 9.3: Employee.cs
2 // Employee class with FirstName, LastName and MonthlySalary properties.
3 public class Employee 4 {
5 private decimal monthlySalaryValue; // monthly salary of employee 6
7 // auto-implemented property FirstName 8 public string FirstName { get; set; } 9
Fig. 9.3 | Employeeclass. (Part 1 of 2.)
10 // auto-implemented property LastName 11 public string LastName { get; set; } 12
13 // constructor initializes first name, last name and monthly salary 14 public Employee( string first, string last, decimal salary )
15 {
16 FirstName = first;
17 LastName = last;
18 MonthlySalary = salary;
19 } // end constructor 20
21 // property that gets and sets the employee's monthly salary 22 public decimal MonthlySalary
23 {
24 get
25 {
26 return monthlySalaryValue;
27 } // end get
28 set
29 {
30 if ( value >= 0M ) // if salary is nonnegative
31 {
32 monthlySalaryValue = value;
33 } // end if
34 } // end set
35 } // end property MonthlySalary 36
37 // return a string containing the employee's information 38 public override string ToString()
39 {
40 return string.Format( "{0,-10} {1,-10} {2,10:C}", 41 FirstName, LastName, MonthlySalary );
42 } // end method ToString 43 } // end class Employee
1 // Fig. 9.4: LINQWithArrayOfObjects.cs
2 // LINQ to Objects using an array of Employee objects.
3 using System;
4 using System.Linq;
5
6 public class LINQWithArrayOfObjects 7 {
8 public static void Main( string[] args )
9 {
10 // initialize array of employees 11 Employee[] employees = {
12 new Employee( "Jason", "Red", 5000M ), 13 new Employee( "Ashley", "Green", 7600M ), 14 new Employee( "Matthew", "Indigo", 3587.5M ),
Fig. 9.4 | LINQ to Objects using an array ofEmployeeobjects. (Part 1 of 3.) Fig. 9.3 | Employeeclass. (Part 2 of 2.)
9.3 Querying an Array ofEmployeeObjects Using LINQ 359
15 new Employee( "James", "Indigo", 4700.77M ), 16 new Employee( "Luke", "Indigo", 6200M ), 17 new Employee( "Jason", "Blue", 3200M ),
18 new Employee( "Wendy", "Brown", 4236.4M ) }; // end init list 19
20 // display all employees
21 Console.WriteLine( "Original array:" );
22 foreach ( var element in employees ) 23 Console.WriteLine( element );
24
25 // filter a range of salaries using && in a LINQ query 26 var between4K6K =
27 28 29 30
31 // display employees making between 4000 and 6000 per month 32 Console.WriteLine( string.Format(
33 "\nEmployees earning in the range {0:C}-{1:C} per month:",
34 4000, 6000 ) );
35 foreach ( var element in between4K6K ) 36 Console.WriteLine( element );
37
38 // order the employees by last name, then first name with LINQ 39 var nameSorted =
40 41 42 43
44 // header
45 Console.WriteLine( "\nFirst employee when sorted by name:" );
46
47 // attempt to display the first result of the above LINQ query
48 if ( )
49 Console.WriteLine( );
50 else
51 Console.WriteLine( "not found" );
52
53 // use LINQ to select employee last names 54 var lastNames =
55 56 57
58 // use method Distinct to select unique last names 59 Console.WriteLine( "\nUnique employee last names:" );
60 foreach ( var element in )
61 Console.WriteLine( element );
62
63 // use LINQ to select first and last names
64 var names =
65 66 67
Fig. 9.4 | LINQ to Objects using an array ofEmployeeobjects. (Part 2 of 3.)
from e in employees
where e.MonthlySalary >= 4000M && e.MonthlySalary <= 6000M select e;
from e in employees
orderby e.LastName, e.FirstName select e;
nameSorted.Any()
nameSorted.First()
from e in employees select e.LastName;
lastNames.Distinct()
from e in employees
select new { e.FirstName, Last = e.LastName };
Accessing the Properties of a LINQ Query’s Range Variable
Line 28 of Fig. 9.4 shows awhereclause that accesses the properties of therange variable.
In this example, the compiler infers that the range variable is of typeEmployeebased on its knowledge thatemployeeswas defined as an array ofEmployeeobjects (lines 11–18).
Anyboolexpression can be used in awhereclause. Line 28 uses the conditional AND (&&) operator to combine conditions. Here, only employees that have a salary between $4,000 and $6,000 per month, inclusive, are included in the query result, which is displayed in lines 35–36.
Sorting a LINQ Query’s Results By Multiple Properties
Line 41 uses anorderbyclause to sort the results according tomultipleproperties—spec- ified in a comma-separated list. In this query, the employees are sorted alphabetically by 68 // display full names
69 Console.WriteLine( "\nNames only:" );
70 foreach ( var element in names ) 71 Console.WriteLine( element );
72
73 Console.WriteLine();
74 } // end Main
75 } // end class LINQWithArrayOfObjects
Original array:
Jason Red $5,000.00
Ashley Green $7,600.00 Matthew Indigo $3,587.50 James Indigo $4,700.77
Luke Indigo $6,200.00
Jason Blue $3,200.00
Wendy Brown $4,236.40
Employees earning in the range $4,000.00-$6,000.00 per month:
Jason Red $5,000.00
James Indigo $4,700.77
Wendy Brown $4,236.40
First employee when sorted by name:
Jason Blue $3,200.00
Unique employee last names:
Red Green Indigo Blue Brown Names only:
{ FirstName = Jason, Last = Red } { FirstName = Ashley, Last = Green } { FirstName = Matthew, Last = Indigo } { FirstName = James, Last = Indigo } { FirstName = Luke, Last = Indigo } { FirstName = Jason, Last = Blue } { FirstName = Wendy, Last = Brown }
Fig. 9.4 | LINQ to Objects using an array ofEmployeeobjects. (Part 3 of 3.)
9.3 Querying an Array ofEmployeeObjects Using LINQ 361
last name. Each group ofEmployees that have the same last name is then sorted within the group by first name.
Any,FirstandCountExtension Methods
Line 48 introduces the query result’sAnymethod, which returnstrueif there’s at least one element, andfalseif there are no elements. The query result’sFirstmethod (line 49) returns the first element in the result. You should check that the query result is not empty (line 48)beforecallingFirst.
We’ve not specified the class that defines methods Firstand Any. Your intuition probably tells you they’re methods declared in theIEnumerable<T> interface, but they aren’t. They’re actuallyextension methods—such methods can be used to enhance a class’s capabilities without modifying the class’s definition. The LINQ extension methods can be used as if they were methods ofIEnumerable<T>.
LINQ defines many more extension methods, such as Count, which returns the number of elements in the results. Rather than usingAny, we could have checked that
Countwas nonzero, but it’s more efficient to determine whether there’s at least one ele- ment than to count all the elements. The LINQ query syntax is actually transformed by the compiler into extension method calls, with the results of one method call used in the next. It’s this design that allows queries to be run on the results of previous queries, as it simply involves passing the result of a method call to another method.
Selecting a Property of an Object
Line 56 uses theselectclause to select the range variable’sLastNameproperty rather than the range variable itself. This causes the results of the query to consist of only the last names (asstrings), instead of completeEmployeeobjects. Lines 60–61 display the unique last names. TheDistinctextension method(line 60) removes duplicate elements, caus- ing all elements in the result to be unique.
Creating New Types in theselectClause of a LINQ Query
The last LINQ query in the example (lines 65–66) selects the propertiesFirstNameand
LastName. The syntax
creates a new object of ananonymous type(a type with no name), which the compiler generates for you based on the properties listed in the curly braces ({}). In this case, the anonymous type consists of properties for the first and last names of the selectedEmployee. TheLastNameproperty is assigned to the propertyLastin theselectclause. This shows how you can specify a new name for the selected property. If you don’t specify a new name, the property’s original name is used—this is the case forFirstNamein this example. The preceding query is an example of aprojection—it performs a transformation on the data.
In this case, the transformation creates new objects containing only theFirstNameand
Lastproperties. Transformations can also manipulate the data. For example, you could give all employees a 10% raise by multiplying theirMonthlySalaryproperties by1.1.
When creating a new anonymous type, you can select any number of properties by specifying them in a comma-separated list within the curly braces ({}) that delineate the anonymous type definition. In this example, the compiler automatically creates a new class having properties FirstName and Last, and the values are copied from the Employee
new { e.FirstName, Last = e.LastName }
objects. These selected properties can then be accessed when iterating over the results.
Implicitly typed local variables allow you to useanonymous typesbecause you do not have to explicitly state the type when declaring such variables.
When the compiler creates an anonymous type, it automatically generates aToString method that returns astringrepresentation of the object. You can see this in the pro- gram’s output—it consists of the property names and their values, enclosed in braces.
Anonymous types are discussed in more detail in Chapter 22.