1.10. Standard Query Operators

LINQ provides an API known as standard query operators (SQOs) to support the kinds of operations we're accustomed to in SQL. You've already used C#'s select and where keywords, which map to LINQ's Select and Where SQOs—which, like all SQOs, are actually methods of the System.Linq.Enumerable static class. Table 1-1 is a complete listing of SQOs.

Table 1-1. LINQ Standard Query Operators Grouped by Operation
OPERATIONOPERATORDESCRIPTION
AggregateAggregateApplies a function over a sequence
 AverageCalculates the average over a sequence
 Count/LongCountCounts the element of a sequence
 MaxReturns the maximum value from a sequence of numeric values
 MinReturns the minimum value from a sequence of numeric values
 SumReturns the sum of all numeric values from a sequence
ConcatenationConcatMerges elements from two sequences
ConversionAsEnumerableConverts the sequence to a generic IEnumerable<T>
 AsQueryableConverts the sequence to a generic IQueryable<T>
 CastCasts an element of the sequence into a specified type
 OfTypeFilters elements of a sequence, returning only those of the specified type
 ToArrayConverts the sequence into an array
 ToDictionaryCreates a Dictionary<K, E> from a sequence
 ToListCreates a List<T> from a sequence
 ToLookupCreates a Lookup<K, T> from a sequence
 ToSequenceReturns its argument typed as IEnumerable<T>
ElementDefaultIfEmptyProvides a default element for an empty sequence
 ElementAtReturns the element at the specified index from a sequence
 ElementAtOrDefaultSimilar to ElementAt but also returns a default element when the specified index is out of range
 FirstReturns the first element in a sequence
 FirstOrDefaultSimilar to First but also returns a default element when the first element in the sequence is not available
 LastReturns the last element in a sequence
 LastOrDefaultSimilar to Last but also returns a default element when the last element in the sequence is not available
 SingleReturns a sequences single element that satisfies a condition specified as an argument
 SingleOrDefaultSimilar to Single but also returns a default value when the single element is not found in the sequence
EqualitySequenceEqualChecks whether two sequences are equal
GenerationEmptyReturns an empty sequence for the specified data type
 RangeGenerates a numeric sequence from a range of two numbers
 RepeatGenerates a sequence by repeating the provided element a specified number of times
GroupingGroupByGroups the elements of a sequence
JoinGroupJoinPerforms a grouped join of two sequences based on matching keys
 JoinPerforms an inner join of two sequences based on matching keys
OrderingOrderByOrders the elements of the sequence according to one or more keys
 OrderByDescendingSimilar to OrderBy but sorts the sequence inversely
 ReverseReverses the elements of the sequence
 ThenByUseful for specifying additional ordering keys after the first one specified by either the OrderBy or OrderByDescending operator
 ThenByDescendingSimilar to ThenBy but sorts the sequence inversely
PartitioningSkipSkips a given number of elements from a sequence and then yields the remainder of the sequence
 SkipWhileSimilar to Skip but the numbers of elements to skip are defined by a Boolean condition
 TakeTakes a given number of elements from a sequence and skips the remainder of the sequence
 TakeWhileSimilar to Take but the numbers of elements to take are defined by a Boolean condition
ProjectionSelectDefines the elements to pick in a sequence
 SelectManyPerforms a one-to-many-elements projection over a sequence
QuantifierAllChecks all the elements of a sequence against the provided condition
 AnyChecks whether any element of the sequence satisfies the provided condition
 ContainsChecks for an element presence into a sequence
RestrictionWhereFilters a sequence based on the provided condition
SetDistinctReturns distinct elements from a sequence
 ExceptProduces a sequence that is the difference between elements of two sequences
 IntersectProduces a sequence resulting from the common elements of two sequences
 UnionProduces a sequence that is the union of two sequences

In the rest of this chapter we'll examine each operator carefully, and consider examples that illustrate the elements' functionality. The examples will be based on numeric sequences for operators that use numbers, and on classes such as Person, Role, and Salary for operators that use more-complex sequences. Listing 1-11 shows these classes.

Example 1-11. The Person, Role, and Salary classes
class Person
{
    int _id;
    int _idRole;
    string _lastName;
    string _firstName;

    public int ID
    {
        get { return _id; }
        set { _id = value; }
    }

public int IDRole
    {
        get { return _idRole; }
        set { _idRole = value; }
    }

    public string LastName
    {
        get { return _lastName; }
        set { _lastName = value; }
    }

    public string FirstName
    {
        get { return _firstName; }
        set { _firstName = value; }
    }
 }

class Role
{
    int _id;
    string _roleDescription;

    public int ID
    {
        get { return _id; }
        set { _id = value; }
    }

    public string RoleDescription
    {
        get { return _roleDescription; }
        set { _roleDescription = value; }
    }
}

class Salary
{
    int _idPerson;
    int _year;
    double _salary;

public int IDPerson
    {
        get { return _idPerson; }
        set { _idPerson = value; }
    }

    public int Year
    {
        get { return _year; }
        set { _year = value; }
    }

    public double SalaryYear
    {
        get { return _salary; }
        set { _salary = value; }
    }
}

The Person class provides four properties, one of which is the matching key with the second class, Role. The Role class provides two public properties to store the role identifier and its description. The Salary class provides the IDPerson foreign key to join to the Person class.

Let's now look at all the operators, starting with the most used ones.

1.10.1. Restriction Operator

There is one restriction operator: Where.

Where

One of the most used LINQ operators is Where. It restricts the sequence returned by a query based on a predicate provided as an argument.

public static IEnumerable<T> Where<T>(
    this IEnumerable<T> source, Func<T, bool> predicate);

public static IEnumerable<T> Where<T>(
    this IEnumerable<T> source, Func<T, int, bool> predicate);

The two forms differ in the second parameter, the predicate. It indicates the condition that has to be checked for each element of a sequence. The second form also accepts an int representing the zero-based index of the element of the source sequence.

Both operators extend the IEnumerable<T> type. Let's look at a couple of examples.

The code snippet in Listing 1-12 uses Where (through the C# here keyword) to retrieve every element in a sequence that has FirstName equal to Brad. Figure 1-5 shows the output.

Example 1-12. The Where Operator in Action
List<Person> people = new List<Person> {
    new Person { ID = 1,
                 IDRole = 1,
                 LastName = "Anderson",
                 FirstName = "Brad"},
    new Person { ID = 2,
                 IDRole = 2,
                 LastName = "Gray",
                 FirstName = "Tom"},
    new Person { ID = 3,
                 IDRole = 2,
                 LastName = "Grant",
                 FirstName = "Mary"},
    new Person { ID = 4,
                 IDRole = 3,
                 LastName = "Cops",
                 FirstName = "Gary"}};

var query = from p in people
            where p.FirstName == "Brad"
            select p; ObjectDumper.Write(query);

Figure 1-5. The output of Listing 1-12

Listing 1-13 uses the second Where form.

Example 1-13. Using Where with an Index
List<Person> people = new List<Person> {
    new Person { ID = 1,
                 IDRole = 1,
                 LastName = "Anderson",
                 FirstName = "Brad"},
    new Person { ID = 2,
                 IDRole = 2,
                 LastName = "Gray",
                 FirstName = "Tom"},
    new Person { ID = 3,
                 IDRole = 2,
                 LastName = "Grant",
                 FirstName = "Mary"},
    new Person { ID = 4,
                 IDRole = 3,
                 LastName = "Cops",
                 FirstName = "Gary"}};

var query = people
           .Where((p, index) => p.IDRole == index);

ObjectDumper.Write(query);

In this case, the condition yields each sequence element whose index equals IDRole. The people data source shows that this is true only for the last element, as you can see in Figure 1-6.

Figure 1-6. The output for the Where example in Listing 1-13

1.10.2. Projection Operators

There are two projection one operators: Select and SelectMany.

Select

Just like SELECT in SQL, the Select operator specifies which elements are to be retrieved.

public static IEnumerable<S> Select<T, S>(
    this IEnumerable<T> source, Func<T, S> selector);

public static IEnumerable<S> Select<T, S>(
    this IEnumerable<T> source, Func<T, int, S> selector);

Both operators extend the IEnumerable<T> type. They differ in the second parameter. The first form accepts a selector function, where we can define the element to pick; the second also accepts a zero-based index indicating the position of the element in the sequence. Let's look at a couple of examples. The code snippet in Listing 1-14 returns all the elements from the sequence, just like SELECT * in SQL. Figure 1-7 shows the output.

Example 1-14. Using the First Form of Select
List<Person> people = new List<Person> {
    new Person { ID = 1,
                 IDRole = 1,
                 LastName = "Anderson",
                 FirstName = "Brad"},
    new Person { ID = 2,
                 IDRole = 2,
                 LastName = "Gray",

FirstName = "Tom"},
    new Person { ID = 3,
                 IDRole = 2,
                 LastName = "Grant",
                 FirstName = "Mary"},
    new Person { ID = 4,
                 IDRole = 3,
                 LastName = "Cops",
                 FirstName = "Gary"}};
var query = from p in people
            select p;

ObjectDumper.Write(query);

Figure 1-7. The output of Listing 1-14

Listing 1-15 uses an index to specify the element position in the sequence.

Example 1-15. Using an Index with Select
List<Person> people = new List<Person> {
    new Person { ID = 1,
                 IDRole = 1,
                 LastName = "Anderson",
                 FirstName = "Brad"},
    new Person { ID = 2,
                 IDRole = 2,
                 LastName = "Gray",
                 FirstName = "Tom"},
    new Person { ID = 3,
                 IDRole = 2,
                 LastName = "Grant",
                 FirstName = "Mary"},
    new Person { ID = 4,
                 IDRole = 3,
                 LastName = "Cops",

FirstName = "Gary"}};

var query = people
            .Select(
                   (p,index) => new { Position=index,
                                      p.FirstName,
                                      p.LastName } );

ObjectDumper.Write(query);

This code snippet creates an anonymous type, formed by the full name of the person anticipated by the element position in the sequence. See Figure 1-8 for the output.

Figure 1-8. The output of Listing 1-15

SelectMany

This operator is similar to Select because it allows us to define the elements to pick from a sequence. The difference is in the return type.

public static IEnumerable<S> SelectMany<T, S>(
    this IEnumerable<T> source,
    Func<T, IEnumerable<S>> selector);

public static IEnumerable<S> SelectMany<T, S>(
    this IEnumerable<T> source,
    Func<T, int, IEnumerable<S>> selector);

With the IEnumerable<S> type returned by the selector parameter of SelectMany, it's possible to concatenate many projection operations together, either on different sequences or starting from the result of a previous query.

The SelectMany operator extends the IEnumerable<T> type. The selector parameter has two formats: the first returns the IEnumerable<S> type and the second also requires a zero-based index that specifies the position of the element in the sequence. Listings 1-16 and 1-17 clarify the differences between Select and SelectMany.

Example 1-16. The SelectMany Method in Action
List<Person> people = new List<Person> {
    new Person { ID = 1,
                 IDRole = 1,
                 LastName = "Anderson",
                 FirstName = "Brad"},
    new Person { ID = 2,
                 IDRole = 2,
                 LastName = "Gray",
                 FirstName = "Tom"},
    new Person { ID = 3,
                 IDRole = 2,
                 LastName = "Grant",
                 FirstName = "Mary"},
    new Person { ID = 4,
                 IDRole = 3,
                 LastName = "Cops",
                 FirstName = "Gary"}};


List<Role> roles = new List<Role> {
    new Role { ID = 1, RoleDescription = "Manager" },
    new Role { ID = 2, RoleDescription = "Developer" }};

var query = from p in people
            where p.ID == 1
            from r in roles
            where r.ID == p.IDRole
            select new { p.FirstName,
                         p.LastName,
                         r.RoleDescription };

ObjectDumper.Write(query);

This code snippet obtains a result similar to a database join, where the result of the first query is used in the other sequence to obtain the element corresponding to the match condition. It's interesting to analyze how the compiler transforms the query expression pattern used in Listing 1-16 to generate the operator method call (see Listing 1-17). Figure 1-9 shows the output.

Example 1-17. After Transformation
var query = people
            .Where(p => p.ID == 1)
            .SelectMany(p => roles
            .Where(r => r.ID == p.ID)
            .Select(r => new { p.FirstName,
                               p.LastName,
                               r.RoleDescription}));

Figure 1-9. The output of Listings 1-16 and 1-17

SelectMany allows us to manage another sequence since it returns an IEnumerable<S>, where S is the sequence. If we use the Select operator instead of SelectMany, we will get an IEnumerable<List<T>>. This object is not composed of the sequence but of List<T> elements.

1.10.3. Join Operators

There are two join operators: Join and GroupJoin.

Join

Like INNER JOIN in SQL, the Join operator combines two sequences based on matching keys supplied as arguments. The Join operator is not overloaded.

public static IEnumerable<V> Join<T, U, K, V>(
    this IEnumerable<T> outer,
    IEnumerable<U> inner,
    Func<T, K> outerKeySelector,
    Func<U, K> innerKeySelector,
    Func<T, U, V> resultSelector);

The Join operator extends the IEnumerable<T> type. The first parameter is one of the two sequences to join. It will be evaluated against the function specified as the outerKeySelector parameter. The second parameter contains the inner sequence used during the evaluation of the inner elements against the function specified as the innerKeySelector parameter. For each matching inner element the resultSelector function, specified as the last parameter, is evaluated for the outer and inner element pair, and the resulting object is returned. Listing 1-18 provides an example. Figure 1-10 shows the output.

Example 1-18. The Join Operator in Action
List<Person> people = new List<Person> {
    new Person { ID = 1,
                 IDRole = 1,
                 LastName = "Anderson",
                 FirstName = "Brad"},
    new Person { ID = 2,
                 IDRole = 2,
                 LastName = "Gray",
                 FirstName = "Tom"},
    new Person { ID = 3,
                 IDRole = 2,
                 LastName = "Grant",
                 FirstName = "Mary"},
    new Person { ID = 4,
                 IDRole = 3,
                 LastName = "Cops",
                 FirstName = "Gary"}};

List<Role> roles = new List<Role> {
  new Role { ID = 1, RoleDescription = "Manager" },
  new Role { ID = 2, RoleDescription = "Developer" }};

var query = from p in people
            join r in roles on p.IDRole equals r.ID
            select new { p.FirstName,
                         p.LastName,
                         r.RoleDescription };

ObjectDumper.Write(query);

Figure 1-10. The output of Listing 1-18

GroupJoin

This operator is similar to Join but it returns the result in an IEnumerable<S> where S is a new sequence.

public static IEnumerable<V> GroupJoin<T, U, K, V>(
    this IEnumerable<T> outer,
    IEnumerable<U> inner,
    Func<T, K> outerKeySelector,
    Func<U, K> innerKeySelector,
    Func<T, IEnumerable<U>, V> resultSelector);

This operator is really useful when we have to implement particular joins, such as SQL's LEFT OUTER join. Listing 1-19 provides and example:

Example 1-19. GroupJoin in Action
List<Person> people = new List<Person> {
    new Person { ID = 1,
                 IDRole = 1,
                 LastName = "Anderson",

FirstName = "Brad"},
    new Person { ID = 2,
                 IDRole = 2,
                 LastName = "Gray",
                 FirstName = "Tom"},
    new Person { ID = 3,
                 IDRole = 2,
                 LastName = "Grant",
                 FirstName = "Mary"},
    new Person { ID = 4,
                 IDRole = 3,
                 LastName = "Cops",
                 FirstName = "Gary"}};

List<Role> roles = new List<Role> {
    new Role { ID = 1, RoleDescription = "Manager" },
    new Role { ID = 2, RoleDescription = "Developer" }};

var query = from p in people
            join r in roles on p.IDRole equals r.ID into pr
            from r in pr.DefaultIfEmpty()
            select new { p.FirstName,
                         p.LastName,
                         RoleDescription = r == null ?
                           "No Role" : r.RoleDescription
                       };

ObjectDumper.Write(query);

In the code snippet in Listing 1-19 the join ... into query expression is used to group the join into a new sequence called pr. Since the new element we introduced in the people sequence has a role identifier that doesn't correspond to any of Role elements in the roles sequence, an empty element is returned. Using the DefaultIfEmpty method, we can replace each empty element with the given ones. In this case no parameter has been provided, so the empty element will be replaced with a null value. By checking this value in the select command we can provide a custom description ("No Role" in our case) when the code encounters null elements. See the output in Figure 1-11.

Figure 1-11. The output of Listing 1-19

1.10.4. Grouping Operator

There is one grouping operator: GroupBy.

GroupBy

Just like the GROUP BY clause of SQL, the GroupBy operator groups elements of a sequence based on a given selector function.

public static IEnumerable<IGrouping<K, T>> GroupBy<T, K>(
    this IEnumerable<T> source,
    Func<T, K> keySelector);

public static IEnumerable<IGrouping<K, T>> GroupBy<T, K>(
    this IEnumerable<T> source,
    Func<T, K> keySelector,
    IEqualityComparer<K> comparer);

public static IEnumerable<IGrouping<K, E>> GroupBy<T, K, E>(
    this IEnumerable<T> source,
    Func<T, K> keySelector,
    Func<T, E> elementSelector);

public static IEnumerable<IGrouping<K, E>> GroupBy<T, K, E>(
    this IEnumerable<T> source,
    Func<T, K> keySelector,
    Func<T, E> elementSelector,
    IEqualityComparer<K> comparer);

Each GroupBy operator returns an IEnumerable<IGrouping<K, E>>. Let's look at how IGrouping<K, T> is declared:

public interface IGrouping<K, T> : IEnumerable<T>
{
    K Key { get; }
}

This interface implements IEnumerable<T> and adds a read-only property called Key. When the code process launches the query (that is when we are going to iterate through elements using a foreach statement) the source parameter is enumerated and evaluated against the keySelector and elementSelector functions (if specified). When every element has been evaluated and each element that satisfies the selector functions has been collected, new instances of the IGrouping<K, E> type are yielded. Finally, the IEqualityComparer interface, when specified, allows us to define a new way to compare elements of a sequence. Let's look at the example in Listing 1-20.

Example 1-20. An Example of GroupBy Using .NET Reflection
var query = from m in typeof(int).GetMethods()
            select m.Name;

ObjectDumper.Write(query);

Console.WriteLine("-=-=-=-=-=-=-=-=-=");
Console.WriteLine("After the GroupBy");
Console.WriteLine("-=-=-=-=-=-=-=-=-=");

var q = from m in typeof(int).GetMethods()
        group m by m.Name into gb
        select new {Name = gb.Key};

ObjectDumper.Write(q);

The first query expression calls the GetMethods method provided by .NET Reflection to retrieve the list of available methods for the int type. Since GetMethods() returns a MethodInfo[] array LINQ query expressions could use it easily too. The first part of the output shows the methods for the int type without the grouping (see Figure 1-12). The second part of the code snippet in Listing 1-20 uses the group by clause to group the elements by method name. The result of the group by clause is inserted into the new IGrouping<K, E> type that provides the Key property representing the by argument of the group by operator. Since the method's name has been promoted to a grouping key, the Key property will be equal to the method's name.

Figure 1-12. The output of Listing 1-20

In Listing 1-21 I added the Count operator to compute the number of method overloads.

Example 1-21. Another Example of group by Clause
var q = from m in typeof(int).GetMethods()
        group m by m.Name into gb

select new {Name = gb.Key, Overloads = gb.Count()};

ObjectDumper.Write(q);

The gb variable represents the result of the group by operation; it's possible to operate against this variable to filter its element, specify a where clause, and so on. In this case the code snippet shows the result of counting the number of elements for each key in the group. In this case it represents the method's overloads. See Figure 1-13 for the output.

Figure 1-13. The output of Listing 1-21

The last example for the grouping operator uses the comparer parameter, which allows us to customize the behavior of the GroupBy method during its work. See Listing 1-22.

Example 1-22. The GroupBy Operator with a Custom Comparison Method
public class MyComparer : IEqualityComparer<string>
{
    public bool Equals(string x, string y) {
        return (x.Substring(0,2)) == (y.Substring(0,2));
    }

    public int GetHashCode(string obj) {
        return (obj.Substring(0,2)).GetHashCode();
    }
}

string[] dictionary = new string[] {"F:Apple", "F:Banana",
                                    "T:House", "T:Phone",
                                    "F:Cherry", "T:Computer"};
var query = dictionary.GroupBy(d => d, new MyComparer());

ObjectDumper.Write(query, 1);

The dictionary array contains two kinds of objects. The F: prefix stands for fruit and the T: prefix stands for thing. We have defined a way to group fruit with fruit and thing with thing. To create a custom comparer we have to define a new class that implements the IEqualityComparer<T> interface. The contract subordinated by this interface forces us to implement two methods: Equals and GetHashCode. For Equals we have to insert the custom logic for our comparer. GetHashCode has to return the hash code for the same string checked in the Equals method. In Listing 1-22 we have a simple way to check the category of the strings. By analyzing their first two characters we know that F: stands for fruit and T: stands for thing. We simply have to check that both strings provided to the Equals method contain the same substring. Figure 1-14 shows the output for Listing 1-22.

Figure 1-14. We have grouped fruits and things.

1.10.5. Ordering Operators

There are five ordering operators: OrderBy, OrderByDescending, ThenBy, ThenByDescending, and Reverse.

OrderBy and OrderByDescending

Like ORDER BY and ORDER BY DESC in SQL, the OrderBy and OrderByDescending operators order elements of a sequence according to a given key. The OrderByDescending operator inverts the ordering.

public static OrderedSequence<T> OrderBy<T, K>(
    this IEnumerable<T> source,
    Func<T, K> keySelector);

public static OrderedSequence<T> OrderBy<T, K>(
    this IEnumerable<T> source,
    Func<T, K> keySelector,
    IComparer<K> comparer);

public static OrderedSequence<T> OrderByDescending<T, K>(
    this IEnumerable<T> source,
    Func<T, K> keySelector);

public static OrderedSequence<T> OrderByDescending<T, K>(
    this IEnumerable<T> source,
    Func<T, K> keySelector,
    IComparer<K> comparer);

The keySelector parameter is used to extract the elements from the sequence. When specified the comparer parameter compares the elements. When the code processes the query, the method collects all the elements and evaluates each of them against the keySelector. Finally, an OrderedSequence<T> type is produced. This is similar to IEnumerable<T> except that it doesn't provide public methods. Listing 1-23 provides an example.

Example 1-23. This Code Snippet Adds the orderby Operator to the .NET Reflection Example
var q = from m in typeof(int).GetMethods()
        orderby m.Name
        group m by m.Name into gb
        select new {Name = gb.Key};

ObjectDumper.Write(q);

The code snippet in Listing 1-23 retrieves the int type's methods ordered by their names. See Figure 1-15 for the output.

Figure 1-15. The output for Listing 1-23

To obtain descending order, you simply add the descending keyword.

orderby m.Name descending

ThenBy and ThenByDescending

As you saw in the previous section, orderby allows us to specify only one ordering key. We have to use either ThenBy or ThenByDescending to concatenate ordering-key values.

public static OrderedSequence<T> ThenBy<T, K>(
    this OrderedSequence<T> source,
    Func<T, K> keySelector);

public static OrderedSequence<T> ThenBy<T, K>(
    this OrderedSequence<T> source,
    Func<T, K> keySelector,

IComparer<K> comparer);

public static OrderedSequence<T> ThenByDescending<T, K>(
    this OrderedSequence<T> source,
    Func<T, K> keySelector);

public static OrderedSequence<T> ThenByDescending<T, K>(
    this OrderedSequence<T> source,
    Func<T, K> keySelector,
    IComparer<K> comparer);

Just like in the OrderBy operators, the first argument is the source sequence whose elements are evaluated against the keySelector parameter. Listing 1-24 shows a more complete OrderBy/ThenBy example.

Example 1-24. The OrderBy and ThenBy Operators
List<Person> people = new List<Person> {
    new Person { ID = 1,
                 IDRole = 1,
                 LastName = "Anderson",
                 FirstName = "Brad"},
    new Person { ID = 2,
                 IDRole = 2,
                 LastName = "Gray",
                 FirstName = "Tom"},
    new Person { ID = 3,
                 IDRole = 2,
                 LastName = "Grant",
                 FirstName = "Mary"},
    new Person { ID = 4,
                 IDRole = 3,
                 LastName = "Cops",
                 FirstName = "Gary"}};


var query = from p in people
            orderby p.FirstName, p.LastName
            select p;

ObjectDumper.Write(query);

When the compiler encounters the query pattern in Listing 1-24 it transforms the first argument of the orderby operator to a call to the OrderBy() method, and transforms every other parameter after the comma to a related ThenBy() method call.

var query = people.OrderBy(p => p.FirstName).
                   ThenBy(p => p.LastName);

Figure 1-16 shows the output of this code snippet.

Figure 1-16. The output of the ordered sequence shown in Listing 1-24

The last example on the ordering operators uses the comparer function (see Listing 1-25).

Example 1-25. Using the comparer Function to Customize the Ordering Behavior
public class MyOrderingComparer : IComparer<string>
    {
       public int Compare(string x, string y)
       {
           x = x.Replace("_",string.Empty);
           y = y.Replace("_",string.Empty);

           return string.Compare(x, y);
       }
    }

string[] dictionary = new string[] {"Apple",
                                    "Banana",
                                    "Cherry"};
var query = dictionary.OrderBy(w => w,

new MyOrderingComparer());

ObjectDumper.Write(query);

To use the comparer parameter function we have to create a new class that implements the IComparer<T> interface. Its contract forces us to define the Compare() method, then add the comparing logic. In the code snippet in Listing 1-25 we want to treat the underscored string as a normal string when the ordering is implemented. So just before the Compare() method is called in the comparer function, we will remove each underscore from the source strings. See the output in Figure 1-17.

Figure 1-17. A custom comparer function allows us to change the ordering of the strings.

NOTE

The May 2006 CTP release doesn't provide support for ordering operators with Visual Studio 2005 IntelliSense.

Reverse

This method simply returns a new sequence with elements in reverse ordering of the source sequence.

public static IEnumerable<T> Reverse<T>(
    this IEnumerable<T> source);

When the code processes the query expression, the method enumerates the elements of the source sequence, collecting them in an IEnumerable<T> type.

Before the method returns the result it inverts the ordering of the elements in the sequence.

1.10.6. Aggregate Operators

There are seven aggregate operators: Count, LongCount, Sum, Min, Max, Average and Aggregate.

Count and LongCount

Those methods return the number of elements within a sequence. The difference between them is in the return type. The Count() method returns an integer and the LongCount() method returns a long type. Let's see the methods' prototypes:

public static int Count<T>(
    this IEnumerable<T> source);

public static int Count<T>(
    this IEnumerable<T> source,
    Func<T, bool> predicate);

public static long LongCount<T>(
    this IEnumerable<T> source);

public static long LongCount<T>(
    this IEnumerable<T> source,
    Func<T, bool> predicate);

Both methods have two different prototypes. The former, without the predicate parameter, checks the type of the source parameter. If it implements the ICollection<T> type then its Count method is used. If it doesn't, the source sequence is enumerated, incrementing a number that represents the final count value. The latter uses the predicate function parameter returning the count of elements against which the specified condition is true.

We have already used the Count operator in the "Grouping Operators" section; see Listing 1-21 for an example of the Count operator.

Sum

The Sum method computes the sum of numeric values within a sequence.

public static Numeric Sum(
    this IEnumerable<Numeric> source);

public static Numeric Sum<T>(
    this IEnumerable<T> source,
    Func<T, Numeric> selector);

The Numeric type returned from the Sum() method must be one of the following: int, int?, long, long?, double, double?, decimal, or decimal?.

NOTE

The ? suffix to the primitive type name specifies that a variable of that type can contain null values. This feature was added to .NET 2.0 to provide greater compatibility with NULLABLE columns in database tables.

The first prototype without the selector parameter computes the sum of the elements in the sequence. When the selector parameter is used it picks the specified element of the sequence on which computing the sum will start. The Sum operator does not include null values in the result, which means a zero will be returned for an empty sequence (see Listing 1-26).

Example 1-26. A Code Snippet for the Sum Operator
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var query = numbers.Sum();
ObjectDumper.Write(query);

The output for the code snippet in Listing 1-26 will be the sum of all the elements in the sequence: 45.

Another great use for the Sum operator is to have it work with the GroupBy operator to obtain total salary amounts, like the one shown in Listing 1-27.

Example 1-27. Using Sum and GroupBy Operators to Obtain Salary Results
List<Person> people = new List<Person> {
    new Person { ID = 1,
                 IDRole = 1,
                 LastName = "Anderson",
                 FirstName = "Brad"},
    new Person { ID = 2,
                 IDRole = 2,
                 LastName = "Gray",
                 FirstName = "Tom"},
    new Person { ID = 3,
                 IDRole = 2,
                 LastName = "Grant",
                 FirstName = "Mary"},
    new Person { ID = 4,
                 IDRole = 3,
                 LastName = "Cops",
                 FirstName = "Gary"}};

List<Salary> salaries = new List<Salary> {
    new Salary { IDPerson = 1,
                 Year = 2004,
                 SalaryYear = 10000.00 },
    new Salary { IDPerson = 1,
                 Year = 2005,
                 SalaryYear = 15000.00 }};

var query = from p in people
            join s in salaries on p.ID equals s.IDPerson
            select new { p.FirstName,
                         p.LastName,
                         s.SalaryYear };

var querySum = from q in query
               group q by q.LastName into gp
               select new { LastName = gp.Key,
                            TotalSalary =

gp.Sum(q => q.SalaryYear) };

ObjectDumper.Write(querySum,1);

The salaries collection contains the total salary per year. The record is related to the people collection through the IDPerson attribute.

The first query joins the two sequences, returning a new anonymous type composed of a person's name and salary. The result is processed again by another query expression, which groups by the LastName attribute and returns a new anonymous type with the total salary for that person. See Figure 1-18 for the output.

Figure 1-18. The output for Listing 1-27

Min and Max

The Min() and Max() methods return the minimum and the maximum element within a sequence, respectively.

public static Numeric Min(
    this IEnumerable<Numeric> source);

public static T Min<T>(
    this IEnumerable<T> source);
public static Numeric Min<T>(
    this IEnumerable<T> source,
    Func<T, Numeric> selector);

public static S Min<T, S>(
    this IEnumerable<T> source,
    Func<T, S> selector);
public static Numeric Max(
    this IEnumerable<Numeric> source);

public static T Max<T>(
    this IEnumerable<T> source);

public static Numeric Max<T>(
    this IEnumerable<T> source,
    Func<T, Numeric> selector);

public static S Max<T, S>(
    this IEnumerable<T> source,
    Func<T, S> selector);

When the code processes the query expression, the Min and Max operators enumerate the source sequence and call the selector for each element, finding the minimum and maximum. When no selector function is specified, the minimum and the maximum are calculated by elements themselves.

Listing 1-28 shows retrieval of the minimum and the maximum salary for the Brad Anders person element.

Example 1-28. Using the Min and Max Operators to Retrieve the Minimum and Maximum Salary
List<Person> people = new List<Person> {
    new Person { ID = 1,
                 IDRole = 1,
                 LastName = "Anderson",
                 FirstName = "Brad"},
    new Person { ID = 2,
                 IDRole = 2,
                 LastName = "Gray",
                 FirstName = "Tom"},
    new Person { ID = 3,
                 IDRole = 2,
                 LastName = "Grant",
                 FirstName = "Mary"},
    new Person { ID = 4,
                 IDRole = 3,
                 LastName = "Cops",
                 FirstName = "Gary"}};

List<Salary> salaries = new List<Salary> {
    new Salary { IDPerson = 1, Year = 2004, SalaryYear = 10000.00 },
    new Salary { IDPerson = 1, Year = 2005, SalaryYear = 15000.00 }};

var query = from p in people
            join s in salaries on p.ID equals s.IDPerson
            where p.ID == 1
            select s.SalaryYear;

Console.WriteLine("Minimum Salary:");
ObjectDumper.Write(query.Min());

Console.WriteLine("Maximum Salary:");
ObjectDumper.Write(query.Max());

From the query expression we retrieve the salaries for the person that has the identifier equal to 1 and then we apply the Min and Max operators to the result. See Figure 1-19 for the output.

Figure 1-19. The Min and Max operators prompting the minimum and maximum salary

Average

This operator computes the average of the elements within a sequence.

public static Result Average(
    this IEnumerable<Numeric> source);

public static Result Average<T>(
    this IEnumerable<T> source,
    Func<T, Numeric> selector);

The Result type returned from the preceding prototypes will be either a double or double? type when the Numeric type is int and long or int? and long?, respectively. When the Numeric type assumes other types, those will be returned as is.

When the average is computed, if the sum of the elements is too large to be contained in the Numeric type an overflow exception will be thrown. Listing 1-29 shows the operator in action.

Example 1-29. Using the Average Operator to Compute the Average of the Salary
List<Person> people = new List<Person> {
    new Person { ID = 1,
                 IDRole = 1,
                 LastName = "Anderson",
                 FirstName = "Brad"},
    new Person { ID = 2,
                 IDRole = 2,
                 LastName = "Gray",
                 FirstName = "Tom"},
    new Person { ID = 3,
                 IDRole = 2,
                 LastName = "Grant",
                 FirstName = "Mary"},
    new Person { ID = 4,
                 IDRole = 3,
                 LastName = "Cops",
                 FirstName = "Gary"}};

List<Salary> salaries = new List<Salary> {
    new Salary { IDPerson = 1, Year = 2004, SalaryYear = 10000.00 },
    new Salary { IDPerson = 1, Year = 2005, SalaryYear = 15000.00 }};

var query = from p in people
            join s in salaries on p.ID equals s.IDPerson
            where p.ID == 1
            select s.SalaryYear;

Console.WriteLine("Average Salary:");
ObjectDumper.Write(query.Average());

From the query expression we retrieve the salaries for the person that has the identifier equal to 1 and then we apply the Average method to the result. See Figure 1-20 for the output.

Figure 1-20. The output for Listing 1-29

Aggregate

This operator allows us to define a function used during the aggregation of the elements of a sequence.

public static T Aggregate<T>(
    this IEnumerable<T> source,
    Func<T, T, T> func);

public static U Aggregate<T, U>(
    this IEnumerable<T> source,
    U seed,
    Func<U, T, U> func);

The difference between those two prototypes stands in the seed parameter. When it is not specified the method uses the specified function to aggregate the elements of the sequence, assuming the first element as seed. When seed is specified the operator uses the seed value as a starting point for applying the aggregate function. Let's look at an example in Listing 1-30:

Example 1-30. The Aggregate Method in Action
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var query = numbers.Aggregate((a,b) => a * b);
ObjectDumper.Write(query);

This code snippet uses the method without the seed parameter, so it takes the first element, 1, as seed, multiplying it for each other element in the sequence. The final result will be 362880.

In Listing 1-31 we will use the seed parameter.

Example 1-31. The Aggregate Method Used with the seed Parameter
int[] numbers = { 9, 3, 5, 4, 2, 6, 7, 1, 8 };
var query = numbers.Aggregate(5, (a,b) => ((a < b) ? (a * b) : a));
ObjectDumper.Write(query);

The method starts evaluating 5 with the first element in the sequence, 9. Since we have defined a rule where the element in the sequence is multiplied by the seed only if it is greater than the aggregated value, the method multiplies those two values, producing 45. This new value will be greater than any of the other elements in the sequence, so the final result will be 45.

1.10.7. Partitioning Operators

There are four partitioning operators: Take, Skip, TakeWhile, and SkipWhile.

Take

The Take method returns a given number of elements within a sequence and ignores the rest.

public static IEnumerable<T> Take<T>(
    this IEnumerable<T> source,
    int count);

When the code processes the query expression, the source sequence is enumerated. This yields elements until the count parameter value is reached.

The Take and Skip methods are really useful when you need to implement a pagination-record mechanism. Listing 1-32 shows an easy approach to the pagination of elements within a sequence.

Example 1-32. Take and Skip Methods to Reproduce a Pagination Mechanism
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9};
var query = numbers.Take(5);
ObjectDumper.Write(query);
Console.Write("Press Enter key to see the other elements...");
Console.ReadLine();
var query2 = numbers.Skip(5);
ObjectDumper.Write(query2);

The first query yields just the first five elements of the sequence. After the Enter key is pressed another query is called, in which the Skip method ignores the first five elements, prompting the rest (see Figure 1-21).

Figure 1-21. The output for Listing 1-32

Skip

This method skips a given number of elements within a sequence, yielding the rest.

public static IEnumerable<T> Skip<T>(
    this IEnumerable<T> source,
    int count);

When the code processes the query expression the source sequence is enumerated, skipping elements until the count parameter value is reached.

See Listing 1-32 and Figure 1-21 for a Skip-method example.

TakeWhile

This method returns the elements from a sequence while the predicate function specified is true.

public static IEnumerable<T> TakeWhile<T>(
    this IEnumerable<T> source,
    Func<T, bool> predicate);

public static IEnumerable<T> TakeWhile<T>(
    this IEnumerable<T> source,
    Func<T, int, bool> predicate);

When the code processes the query expression the source sequence is enumerated, testing each element against the predicate function. Each element that satisfies the condition is yielded. The second prototype provides a zero-based index related to the elements of the sequence.

Listing 1-33 provides an example of the TakeWhile and SkipWhile methods.

Example 1-33. The TakeWhile and SkipWhile Methods in Action
int[] numbers = { 9, 3, 5, 4, 2, 6, 7, 1, 8 };
var query = numbers.TakeWhile((n, index) => n >= index);
ObjectDumper.Write(query);
Console.Write("Press Enter key to see the other elements...");
Console.ReadLine();
var query2 = numbers.SkipWhile((n, index) => n >= index);
ObjectDumper.Write(query2);

This code snippet uses the TakeWhile second prototype, where the index of the elements of the sequence acts as a condition of the predicate function. Until the element's index is less than or equal to its own value, it is yielded. The rest of the elements will be skipped. After the Enter key is pressed the SkipWhile method is used with the same predicate condition to yield the other elements. See Figure 1-22 for the resulting output.

Figure 1-22. The output for Listing 1-33

SkipWhile

The SkipWhile operator skips elements from a sequence while the predicate function returns true, then it yields the rest.

public static IEnumerable<T> SkipWhile<T>(
    this IEnumerable<T> source,
    Func<T, bool> predicate);

public static IEnumerable<T> SkipWhile<T>(
    this IEnumerable<T> source,
    Func<T, int, bool> predicate);

Each source element is tested against the predicate function parameter. The element will be skipped if the predicate function returns true. The second prototype provides a zero-based index related to the elements of the sequence.

For an example of the SkipWhile method see Listing 1-33.

1.10.8. Concatenation Operator

There is one concatenation operator: Concat.

Concat

This operator concatenates two sequences.

public static IEnumerable<T> Concat<T>(
    this IEnumerable<T> first,
    IEnumerable<T> second);

The resulting IEnumerable<T> type is the concatenation of the first and second sequences specified as a parameter.

In Listing 1-34 two numeric sequences are concatenated.

Example 1-34. The Concat Method Used to Concatenate Two Numeric Sequences
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int[] moreNumbers = {10, 11, 12, 13};
var query = numbers.Concat(moreNumbers);
ObjectDumper.Write(query);

Starting from the numbers sequence the Concat method appends the moreNumbers sequence (see Figure 1-23).

Figure 1-23. The output of Listing 1-34

1.10.9. Element Operators

There are nine element operators: First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, ElementAt, ElementAtOrDefault, and DefaultIfEmpty.

First, Last, FirstOrDefault, and LastOrDefault

These operators return the first/last element from a sequence.

public static T First<T>(
    this IEnumerable<T> source);

public static T First<T>(
    this IEnumerable<T> source,
    Func<T, bool> predicate);

public static T FirstOrDefault<T>(
    this IEnumerable<T> source);

public static T FirstOrDefault<T>(
    this IEnumerable<T> source,
    Func<T, bool> predicate);

public static T Last<T>(
    this IEnumerable<T> source);

public static T Last<T>(
    this IEnumerable<T> source,
    Func<T, bool> predicate);

public static T LastOrDefault<T>(
    this IEnumerable<T> source);

public static T LastOrDefault<T>(
    this IEnumerable<T> source,
    Func<T, bool> predicate);

When the predicate function parameter is specified, the method returns the first/last element against which the predicate function is satisfied, and therefore returns true. Otherwise the method returns simply the first/last element in the sequence. Listing 1-35 provides some examples.

Example 1-35. Examples of the First and Last Methods
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9};
var query = numbers.First();
Console.WriteLine("The first element in the sequence");
ObjectDumper.Write(query);
query = numbers.Last();
Console.WriteLine("The last element in the sequence");
ObjectDumper.Write(query);
Console.WriteLine("The first even element in the sequence");
query = numbers.First(n => n % 2 == 0);
ObjectDumper.Write(query);
Console.WriteLine("The last even element in the sequence");
query = numbers.Last(n => n % 2 == 0);
ObjectDumper.Write(query);

In Listing 1-35 the First and Last methods are used to retrieve the first and last element of the numeric sequence, respectively. Moreover, when the predicate function is specified the First and Last methods return the first and last even element, respectively (see Figure 1-24).

Figure 1-24. Sample output of the First and Last methods

Using the FirstOrDefault/LastOrDefault methods we would have obtained the same results. However, when we use those methods and a predicate does not find an element satisfying the specified condition, a default value is returned (thereby avoiding retrieval of an exception). See the example in Listing 1-36.

Example 1-36. A FirstOrDefault/LastOrDefault Example
int[] numbers = {1, 3, 5, 7, 9};
var query = numbers.FirstOrDefault(n => n % 2 == 0);
Console.WriteLine("The first even element in the sequence");
ObjectDumper.Write(query);
Console.WriteLine("The last odd element in the sequence");
query = numbers.LastOrDefault(n => n % 2 == 1);
ObjectDumper.Write(query);

Since no even numbers are in the sequence, FirstOrDefault returns the zero default value. On the other hand, the LastOrDefault operator looks for the last odd number in the sequence and finds the number 9. Figure 1-25 shows the output.

Figure 1-25. The output for Listing 1-36

Single and SingleOrDefault

These methods return a single element picked from a sequence.

public static T Single<T>(
    this IEnumerable<T> source);

public static T Single<T>(
    this IEnumerable<T> source,
    Func<T, bool> predicate);

public static T SingleOrDefault<T>(
    this IEnumerable<T> source);

public static T SingleOrDefault<T>(
    this IEnumerable<T> source,
    Func<T, bool> predicate);

When the predicate function is specified it will be used against each element until the function returns true. The element that satisfies the predicate will be returned. If more than one element satisfies the predicate function, an exception will be thrown. In Listing 1-37 just one element (9) satisfies the predicate condition that the elements must be greater than 8.

Example 1-37. An Example of Single with a Predicate Condition
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9};
var query = numbers.Single(n => n > 8);
ObjectDumper.Write(query);

Using the Single method, if no element satisfies the predicate condition, an exception is thrown. Using the SingleOrDefault method (see Listing 1-38) either a null or zero value is returned when no element satisfies the predicate function. The difference between the null and zero value depends on the source type: null for reference types (i.e., strings) and zero for value types (i.e., integers).

Example 1-38. The SingleOrDefault Method in Action
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9};
var query = numbers.SingleOrDefault(n => n > 9);
ObjectDumper.Write(query);

Since no numeric element is greater than nine, a zero value will be returned.

ElementAt and ElementAtOrDefault

These methods return an element from the sequence at the specified zero-based index.

public static T ElementAt<T>(
    this IEnumerable<T> source,
    int index);

public static T ElementAtOrDefault<T>(
    this IEnumerable<T> source,
    int index);

When the code processes the query expression the method checks if the sequence implements the IList<T> type. If so the method uses the IList<T> implementation to obtain the element; otherwise the sequence will be enumerated until the index is reached.

Listing 1-39 uses ElementAt to retrieve the number 5 from the sequence.

Example 1-39. Using ElementAt to Retrieve the Fifth Element
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9};
var query = numbers.ElementAt(4);
ObjectDumper.Write(query);

When an invalid index is specified (i.e., an index less than zero) an exception of type ArgumentNullException is thrown. On the other hand, when using the ElementAtOrDefault method either a null or zero value will be returned (see Listing 1-40).

Example 1-40. ElementAtOrDefault in Action
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9};
var query = numbers.ElementAtOrDefault(9);
ObjectDumper.Write(query);

Since the tenth element is out of range the zero default value is returned.

DefaultIfEmpty

This operator replaces an empty element with a default element in a sequence, as in the following examples.

public static IEnumerable<T> DefaultIfEmpty<T>(
    this IEnumerable<T> source);

public static IEnumerable<T> DefaultIfEmpty<T>(
    this IEnumerable<T> source,
    T defaultValue);

If no default value parameter is specified, a null element will be yielded. This method is useful to produce left outer joins. See Listing 1-19 for a code-snippet sample.

1.10.10. Generation Operators

There are three generation operators: Range, Repeat, and Empty.

Empty

This operator returns an empty sequence of the specified type.

public static IEnumerable<T> Empty<T>();

When the IEnumerable<T> returned by the Empty<T> method is enumerated it yields nothing. Listing 1-41 shows how to produce an empty Person sequence:

Example 1-41. An Empty Person Sequence Produced by the Empty<T> Method
IEnumerable<Person> p = Sequence.Empty<Person>();
ObjectDumper.Write(p);

The p variable contains no values, so the Write() method will not prompt any information.

Range

This operator produces a range of numeric values.

public static IEnumerable<int> Range(
    int start,
    int count);

When the IEnumerable<int> type is enumerated it produces a sequence of count elements starting from the start parameter value. In Listing 1-42 a sequence of ten numbers will be generated.

Example 1-42. A Numeric Sequence from 1 to 10 Is Generated
ObjectDumper.Write(Sequence.Range(1, 10));

Repeat

This operator produces a sequence by repeating a value a given number of times.

public static IEnumerable<T> Repeat<T>(
    T element, int count);

The T element parameter will be generated the number of times indicated by the count parameter. In Listing 1-43 the first element of the Person sequence (people) is repeated ten times.

Example 1-43. The Repeat Method in Action
List<Person> people = new List<Person> {
    new Person { ID = 1,
                 IDRole = 1,
                 LastName = "Anderson",
                 FirstName = "Brad"},
    new Person { ID = 2,
                 IDRole = 2,
                 LastName = "Gray",
                 FirstName = "Tom"},
    new Person { ID = 3,
                 IDRole = 2,
                 LastName = "Grant",
                 FirstName = "Mary"},
    new Person { ID = 4,
                 IDRole = 3,
                 LastName = "Cops",
                 FirstName = "Gary"}};

IEnumerable<Person> p = Sequence.Repeat(people[0], 10);

ObjectDumper.Write(p);

Figure 1-26 shows the output for the Listing 1-43.

Figure 1-26. The output of Listing 1-43

1.10.11. Quantifier Operators

There are three quantifiers: All, Any, and Contains.

All

This operator uses the predicate function against the elements of a sequence and returns true if all of them satisfy the predicate condition. Let's see the method's prototype:

public static bool All<T>(
    this IEnumerable<T> source,
    Func<T, bool> predicate);

The source sequence is enumerated and each element is used against the predicated function condition. If all of them satisfy the predicate condition then a true value is returned. Listing 1-44 uses the predicate to understand if all of sequence's elements are even. The output is Yes, they are.

Example 1-44. Using the All method to Find Out If All of a Sequence's Elements Are Even
int[] numbers = { 2, 6, 24, 56, 102 };
Console.WriteLine("Are those all even numbers?");
ObjectDumper.Write(
    numbers.All(e => e % 2 == 0) ?
        "Yes, they are" : "No, they aren't");

Any

This operator searches a sequence for elements that satisfy the specified condition.

public static bool Any<T>(
    this IEnumerable<T> source);

public static bool Any<T>(
    this IEnumerable<T> source,
    Func<T, bool> predicate);

The predicate function is checked against each element of the source sequence, and stops as soon as the condition is satisfied. When the predicate parameter is not specified, the method returns a true value if the sequence is not empty. Listing 1-45 uses the predicate to understand if at least one element of the numeric sequence is odd. The output is No, there isn't.

Example 1-45. Using the Any Method to Search for an Odd Numeric Value
int[] numbers = { 2, 6, 24, 56, 102 };
Console.WriteLine("Is there at least oneodd number?");
ObjectDumper.Write(
    numbers.Any(e => e % 2 == 1) ?
        "Yes, there is" : "No, there isn't");

Contains

This operator looks for a specified type within the sequence and returns true when the element is found.

public static bool Contains<T>(
    this IEnumerable<T> source,
    T value);

If the source sequence implements the ICollection<T> type then its Contains method will be used to search for the specified value. Otherwise the source sequence will be enumerated and each element will be compared to the value parameter until the element is found or the enumeration is over. A true value is returned when the element is found. If no element is found, a false value is returned. Listing 1-46 searches for and finds the number 102 within the sequence, so the output is Yes, there is.

Example 1-46. The Contains Method Searches for the Specified Value in the Sequence.
int[] numbers = { 2, 6, 24, 56, 102 };
Console.WriteLine("Is there the number 102?");
ObjectDumper.Write(numbers.Contains(102) ?
    "Yes, there is" : "No, there isn't");

1.10.12. Equality Operator

There is one equality operator: SequenceEqual.

SequenceEqual

This operator compares two sequences and returns true when their elements are equal.

public static bool SequenceEqual<T>(
    this IEnumerable<T> first,
    IEnumerable<T> second);

Under the hood the code uses the other version of the SequenceEqual method that accepts an IEqualityComparer parameter. This method allows us to provide either a standard comparer such as the one created from the EqualityComparer class or a customized one. The SequenceEqual method will return a true Boolean value if both sequences contain the same elements (see Listing 1-47.)

Example 1-47. Using the SequenceEqual Method with Equal and Unequal Sequences
int[] sequence1 = { 1, 2, 3, 4, 5 };
int[] sequence2 = { 1, 2, 3, 4, 5 };

Console.WriteLine("Are those sequence equal?");

ObjectDumper.Write(sequence1.SequenceEqual(sequence2) ?
    "Yes, they are" : "No, they aren't");

int[] sequence3 = { 1, 2, 3, 4, 5 };
int[] sequence4 = { 5, 4, 3, 2, 1 };

Console.WriteLine("Are those sequence equal?");
ObjectDumper.Write(sequence3.SequenceEqual(sequence4) ?
    "Yes, they are" : "No, they aren't");

Listing 1-47 starts comparing two sequences, sequence1 and sequence2. It compares the first element (1) of the first sequence with the first element (1) of the second sequence. Since they are equal, the method moves on to the other elements. The two sequences are equal, so the final output will be Yes, they are.

The next two sequences are different because the first element of the fourth sequence (1) is not equal to the first element of the fifth sequence (5). A false value is returned immediately and the output of the code is No, they aren't.

1.10.13. Set Operators

There are four set operators: Distinct, Intersect, Union, and Except.

Distinct

This operator is similar to the DISTINCT keyword used in SQL; it eliminates duplicates from a sequence.

public static IEnumerable<T> Distinct<T>(
    this IEnumerable<T> source);

When the code processes the query it enumerates the element of the sequence, storing into an IEnumerable<T> type each element that has not been stored previously. In Listing 1-48 the Distinct operator selects unique values from the sequence. The output will be 1, 2, 3.

Example 1-48. The Distinct Operator in Action
int[] numbers = {1, 1, 2, 3, 3};
ObjectDumper.Write(numbers.Distinct());

Intersect

This operator returns a sequence made by common elements of two different sequences.

public static IEnumerable<T> Intersect<T>(
    this IEnumerable<T> first,
    IEnumerable<T> second);

The first sequence is enumerated and compared to the second one. Only the common element will be collected and inserted into the IEnumerable<T> return type. In Listing 1-49 the Intersect method compares two numeric sequences and returns the common elements: 1 and 3.

Example 1-49. The Intersect Method Used to Retrieve Common Elements in Two Sequences
int[] numbers = {1, 1, 2, 3, 3};
int[] numbers2 = {1, 3, 3, 4};
ObjectDumper.Write(numbers.Intersect(numbers2));

Union

This operator returns a new sequence formed by uniting the two different sequences.

public static IEnumerable<T> Union<T>(
    this IEnumerable<T> first,
    IEnumerable<T> second);

The first sequence is enumerated and distinct elements are stored into an IEnumerable<T> type. The second sequence is enumerated as well and the elements not stored previously are added to the IEnumerable<T> return type. In Listing 1-50 the Union operator returns an IEnumerable<int> type composed of distinct elements from the two numeric sequences: 1, 3, 2, and 4.

Example 1-50. The Union Operator in Action
int[] numbers = {1, 1, 3, 3};
int[] numbers2 = {1, 2, 3, 4};
ObjectDumper.Write(numbers.Union(numbers2));

NOTE

The Union operator doesn't sort the numbers when it produces the IEnumerable<T> return type.

Except

This operator produces a new sequence composed of the elements of the first sequence not present in the second sequence.

public static IEnumerable<T> Except<T>(
    this IEnumerable<T> first,
    IEnumerable<T> second);

When the code processes the query expression it starts to enumerate the first sequence, storing its distinct elements in an IEnumerable<T> type. Then it enumerates the second sequence and removes the common elements into the IEnumerable<T> type stored previously. Finally, it returns the processed IEnumerable<T> type to the caller. The output for the example shown in Listing 1-51 is 2, 4.

Example 1-51. The Except Method in Action
int[] numbers = {1, 2, 3, 4};
int[] numbers2 = {1, 1, 3, 3};
ObjectDumper.Write(numbers.Except(numbers2));

1.10.14. Conversion Operators

There are seven conversion operators: OfType, Cast, ToSequence, ToArray, ToList, ToDictionary, and ToLookup.

OfType

This operator produces a new IEnumerable<T> type composed of only the element of the specified type.

public static IEnumerable<T> OfType<T>(
    this IEnumerable source);

The operator enumerates the elements of the source sequence, searching for those whose type is equal to T. Only those elements will be inserted in the final IEnumerable<T> sequence that the OfType method returns. Listing 1-52 searches for the elements of double type in the sequence. The result is 2.0.

Example 1-52. The OfType Searches for the Specified Type T in the Sequence.
object[] sequence = {1, "Hello", 2.0};
ObjectDumper.Write(sequence.OfType<double>());

Cast

This operator casts the elements of the sequence to a given type.

public static IEnumerable<T> Cast<T>(
    this IEnumerable source);

The operator enumerates the elements of the source sequence and casts its elements to the T type. Those new elements are collected into a new IEnumerable<T> type that will be returned. Listing 1-53 casts object type to double type. The output will be 1.0, 2.0, 3.0.

Example 1-53. The Cast Operator in Action
object[] doubles = {1.0, 2.0, 3.0};
IEnumerable<double> d = doubles.Cast<double>();
ObjectDumper.Write(d);

ToSequence

This operator simply returns the typed sequence to a given IEnumerable<T> type.

public static IEnumerable<T> ToSequence<T>(
    this IEnumerable<T> source);

This operator has no effect on the source sequence other than changing the type to IEnumerable<T>. This could be useful to call a standard query expression when a type implements its own query-expression methods.

ToArray

This operator returns an array composed of the elements of the source sequence.

public static T[] ToArray<T>(
    this IEnumerable<T> source);

In Listing 1-54 the elements of the people sequence that have the LastName length equal to 4 are retrieved and inserted into a string array.

Listing 1-54 does not use the ObjectDumper's Write method; eliminating it allowed me to demonstrate more clearly that the result of the query has been converted into an array. The output is Gray, Cops.

Example 1-54. The Query Result Is Inserted into an Array Using the ToArray Operator.
List<Person> people = new List<Person> {
    new Person { ID = 1,
                 IDRole = 1,
                 LastName = "Anderson",
                 FirstName = "Brad"},
    new Person { ID = 2,
                 IDRole = 2,
                 LastName = "Gray",
                 FirstName = "Tom"},
    new Person { ID = 3,
                 IDRole = 2,
                 LastName = "Grant",
                 FirstName = "Mary"},
    new Person { ID = 4,
                 IDRole = 3,
                 LastName = "Cops",
                 FirstName = "Gary"}};

var query = from p in people
            where p.LastName.Length == 4
            select p.LastName;

string[] names = query.ToArray();

for(int i=0; i<names.Length; i++)
    Console.WriteLine(names[i]);

ToList

This operator returns a List<T> type composed of the elements of the source sequence.

public static List<T> ToList<T>(
    this IEnumerable<T> source);

In Listing 1-55 the result of the query is converted into a List<string> type. The output of this code snippet is Gray, Cops.

Example 1-55. The ToList Method in Action
var query = from p in people
            where p.LastName.Length == 4
            select p.LastName;

List<string> names = query.ToList<string>();
ObjectDumper.Write(names);

ToDictionary

This operator returns a Dictionary<K, E> type composed of the elements of a sequence.

public static Dictionary<K, T> ToDictionary<T, K>(
    this IEnumerable<T> source,
    Func<T, K> keySelector);

public static Dictionary<K, T> ToDictionary<T, K>(
    this IEnumerable<T> source,
    Func<T, K> keySelector,
    IEqualityComparer<K> comparer);

public static Dictionary<K, E> ToDictionary<T, K, E>(
    this IEnumerable<T> source,
    Func<T, K> keySelector,
    Func<T, E> elementSelector);

public static Dictionary<K, E> ToDictionary<T, K, E>(
    this IEnumerable<T> source,
    Func<T, K> keySelector,
    Func<T, E> elementSelector,
    IEqualityComparer<K> comparer);

The prototype with both keySelector and elementSelector parameters is used to specify the elements of the sequence promoted to be the dictionary's key and value, respectively. When the elementSelector parameter is omitted the value will be the element itself. Finally, the prototype with the comparer parameter allows us to define a custom comparer function used during the Dictionary<K, E> type construction.

In Listing 1-56 .NET Reflection and LINQ are used to retrieve the int type's methods, which will be inserted into a Dictionary<string, int> type.

Example 1-56. Using ToDictionary() to Retrieve a Dictionary<string, int> Type
var q = from m in typeof(int).GetMethods()
        group m by m.Name into gb
        select gb;

Dictionary<string, int> d =
    q.ToDictionary(k => k.Key, k => k.Count());

The query groups the methods of the int type. The ToDictionary() method is used to retrieve a dictionary with keys equal to the methods' name, and values equal to the methods' overloads number.

ToLookup

This operator returns a Lookup<K, T> type composed of elements from the source sequence.

public static Lookup<K, T> ToLookup<T, K>(
    this IEnumerable<T> source,
    Func<T, K> keySelector);

public static Lookup<K, T> ToLookup<T, K>(
    this IEnumerable<T> source,
    Func<T, K> keySelector,
    IEqualityComparer<K> comparer);

public static Lookup<K, E> ToLookup<T, K, E>(
    this IEnumerable<T> source,
    Func<T, K> keySelector,
    Func<T, E> elementSelector);

public static Lookup<K, E> ToLookup<T, K, E>(
    this IEnumerable<T> source,
    Func<T, K> keySelector,
    Func<T, E> elementSelector,
    IEqualityComparer<K> comparer);

public class Lookup<K, T> : IEnumerable<IGrouping<K, T>>
 {
     public int Count { get; }
     public IEnumerable<T> this[K key] { get; }
     public bool Contains(K key);
     public IEnumerator<IGrouping<K, T>> GetEnumerator();
 }

This new Lookup<K, T> type differs from Dictionary<K, E> type in the implementation of the type itself. The former allows us to associate a key with a sequence of values. The latter allows us to associate a key with a single value.

The prototype with both keySelector and elementSelector parameters is used to specify the elements of the sequence promoted to be the lookup's key and value, respectively. When the elementSelector parameter is omitted the value will be the element itself. Finally, the prototype with the comparer parameter allows us to define a custom comparer function used during the Lookup<K, T> type construction.

Listing 1-57 creates a Lookup<string, Salary> type whose key is equal to the Year element of the salaries sequence; the related value is the salary element itself.

Example 1-57. The ToLookup Method Converts the Query Expression Result into a Lookup<string, Salary> Type.
List<Person> people = new List<Person> {
    new Person { ID = 1,
                 IDRole = 1,
                 LastName = "Anderson",
                 FirstName = "Brad"},
    new Person { ID = 2,
                 IDRole = 2,
                 LastName = "Gray",
                 FirstName = "Tom"},
    new Person { ID = 3,
                 IDRole = 2,
                 LastName = "Grant",

FirstName = "Mary"},
    new Person { ID = 4,
                 IDRole = 3,
                 LastName = "Cops",
                 FirstName = "Gary"}};

List<Salary> salaries = new List<Salary> {
    new Salary { IDPerson = 1, Year = 2004, SalaryYear = 10000.00 },
    new Salary { IDPerson = 1, Year = 2005, SalaryYear = 15000.00 }};

IEnumerable<Salary> q = from p in people
                        where p.ID == 1
                        from s in salaries
                        where s.IDPerson == p.ID
                        select s;

ILookup<string, Salary> d =
    q.ToLookup(k => k.Year.ToString(), k => k);

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.232.152