Appendix . LINQ standard query operators

There are many standard query operators in LINQ, only some of which are supported directly in C# query expressions—the others have to be called “manually” as normal methods. Some of the standard query operators are demonstrated in the main text of the book, but they’re all listed in this appendix. For the examples, I’ve defined two sample sequences:

string[] words = {"zero", "one", "two", "three", "four"};
int[] numbers = {0, 1, 2, 3, 4};

For completeness I’ve included the operators we’ve already seen, although in most cases chapter 11 contains more detail on them than I’ve provided here. For each operator, I’ve specified whether it uses deferred or immediate execution.

Aggregation

The aggregation operators (see table A.1) all result in a single value rather than a sequence. Average and Sum all operate either on a sequence of numbers (any of the built-in numeric types) or on a sequence of elements with a delegate to convert from each element to one of the built-in numeric types. Min and Max have overloads for numeric types, but can also operate on any sequence either using the default comparer for the element type or using a conversion delegate. Count and LongCount are equivalent to each other, just with different return types. Both of these have two overloads—one that just counts the length of the sequence, and one that takes a predicate: only elements matching the predicate are counted.

Table A.1. Examples of aggregation operators

Expression

Result

numbers.Sum()

10

numbers.Count()

5

numbers.Average()

2

numbers.LongCount(x => x%2 == 0)

3 (as a long; there are three even numbers)

words.Min(word => word.Length)

3 ("one" and "two")

words.Max(word => word.Length)

5 ("three")

numbers.Aggregate("seed",
  (soFar, elt) => soFar+elt.ToString(),
  result => result.ToUpper())

SEED01234

The most generalized aggregation operator is just called Aggregate. All the other aggregation operators could be expressed as calls to Aggregate, although it would be relatively painful to do so. The basic idea is that there’s always a “result so far,” starting with an initial seed. An aggregation delegate is applied for each element of the input sequence: the delegate takes the result so far and the input element, and produces the next result. As a final optional step, a conversion is applied from the aggregation result to the return value of the method. This conversion may result in a different type, if necessary. It’s not quite as complicated as it sounds, but you’re still unlikely to use it very often.

All of the aggregation operators use immediate execution.

Concatenation

There is a single concatenation operator: Concat (see table A.2). As you might expect, this operates on two sequences, and returns a single sequence consisting of all the elements of the first sequence followed by all the elements of the second. The two input sequences must be of the same type, and execution is deferred.

Table A.2. Concat example

Expression

Result

numbers.Concat(new[] {2, 3, 4, 5, 6})
0, 1, 2, 3, 4, 2, 3, 4, 5, 6

Conversion

The conversion operators (see table A.3) cover a fair range of uses, but they all come in pairs. AsEnumerable and AsQueryable allow a sequence to be treated as IEnumerable <T> or IQueryable respectively, forcing further calls to convert lambda expressions into delegate instances or expression trees respectively, and use the appropriate extension methods. These operators use deferred execution.

Table A.3. Conversion examples

Expression

Result

allStrings.Cast<string>()
"These", "are", "all", "strings"
(as IEnumerable<string>)
allStrings.OfType<string>()
"These", "are", "all", "strings"
(as IEnumerable<string>)
notAllStrings.Cast<string>()
Exception is thrown while iterating, at point of failing conversion
notAllStrings.OfType<string>()
"Number", "at", "the", "end"
(as IEnumerable<string>)
numbers.ToArray()
0, 1, 2, 3, 4
(as int[])
numbers.ToList()
0, 1, 2, 3, 4
(as List<int>)
words.ToDictionary(word =>
  word.Substring(0, 2)
)
Dictionary contents:
"ze": "zero"
"on": "one"
"tw": "two"
"th": "three"
"fo": "four"
// Key is first character of word
words.ToLookup(word => word[0])
Lookup contents:
'z': "zero"
'o': "one"
't': "two", "three"
'f': "four"
words.ToDictionary(word => word[0])
Exception: Can only have one entry per key, so
fails on 't'

ToArray and ToList are fairly self-explanatory: they read the whole sequence into memory, returning it either as an array or as a List<T>. Both use immediate execution.

Cast and OfType convert an untyped sequence into a typed one, either throwing an exception (for Cast) or ignoring (for OfType) elements of the input sequence that aren’t implicitly convertible to the output sequence element type. This may also be used to convert typed sequences into more specifically typed sequences, such as converting IEnumerable<object> to IEnumerable<string>. The conversions are performed in a streaming manner with deferred execution.

ToDictionary and ToLookup both take delegates to obtain the key for any particular element; ToDictionary returns a dictionary mapping the key to the element type, whereas ToLookup returns an appropriately typed ILookup<,>. A lookup is like a dictionary where the value associated with a key isn’t one element but a sequence of elements. Lookups are generally used when duplicate keys are expected as part of normal operation, whereas a duplicate key will cause ToDictionary to throw an exception. More complicated overloads of both methods allow a custom IEqualityComparer<T> to be used to compare keys, and a conversion delegate to be applied to each element before it is put into the dictionary or lookup.

The examples in table A.3 use two additional sequences to demonstrate Cast and OfType:

object[] allStrings = {"These", "are", "all", "strings"};

object[] notAllStrings = {"Number", "at", "the", "end", 5};

I haven’t provided examples for AsEnumerable or AsQueryable because they don’t affect the results in an immediately obvious way. Instead, they affect the manner in which the query is executed. Queryable.AsQueryable is an extension method on IEnumerable that returns an IQueryable (both types being generic or nongeneric, depending on which overload you pick). If the IEnumerable you call it on is already an IQueryable, it just returns the same reference—otherwise it creates a wrapper around the original sequence. The wrapper allows you to use all the normal Queryable extension methods, passing in expression trees, but when the query is executed the expression tree is compiled into normal IL and executed directly, using the LambdaExpression.Compile method shown in section 9.3.2.

Enumerable.AsEnumerable is an extension method on IEnumerable<T> and has a trivial implementation, simply returning the reference it was called on. No wrappers are involved—it just returns the same reference. This forces the Enumerable extension methods to be used in subsequent LINQ operators. Consider the following query expressions:

// Filter the users in the database with LIKE
from user in context.Users
where user.Name.StartsWith("Tim")
select user;

// Filter the users in memory
from user in context.Users.AsEnumerable()
where user.Name.StartsWith("Tim")
select user;

The second query expression forces the compile-time type of the source to be IEnumerable<User> instead of IQueryable<User>, so all the processing is done in memory instead of at the database. The compiler will use the Enumerable extension methods (taking delegate parameters) instead of the Queryable extension methods (taking expression tree parameters). Normally you want to do as much processing as possible in SQL, but when there are transformations that require “local” code, you sometimes have to force LINQ to use the appropriate Enumerable extension methods.

Element operations

This is another selection of query operators that are grouped in pairs (see table A.4). This time, the pairs all work the same way. There’s a simple version that picks a single element if it can or throws an exception if the specified element doesn’t exist, and a version with OrDefault at the end of the name. The OrDefault version is exactly the same except that it returns the default value for the result type instead of throwing an exception if it can’t find the element you’ve asked for. All of these operators use immediate execution.

Table A.4. Single element selection examples

Expression

Result

words.ElementAt(2)

"two"

words.ElementAtOrDefault(10)

null

words.First()

"zero"

words.First(word => word.Length==3)

"one"

words.First(word => word.Length==10)

Exception: No matching elements

words.FirstOrDefault
  (word => word.Length==10)
null

words.Last()

"four"

words.Single()

Exception: More than one element

words.SingleOrDefault()

null

words.Single(word => word.Length==5)

"three"

words.Single(word => word.Length==10)

Exception: No matching elements

The operator names are easily understood: First and Last return the first and last elements of the sequence respectively (only defaulting if there are no elements), Single returns the only element in a sequence (defaulting if there isn’t exactly one element), and ElementAt returns a specific element by index (the fifth element, for example). In addition, there’s an overload for all of the operators other than ElementAt to filter the sequence first—for example, First can return the first element that matches a given condition.

Equality operations

There’s only one equality operation: SequenceEqual (see table A.5). This just compares two sequences for element-by-element equality, including order. For instance, the sequence 0, 1, 2, 3, 4 is not equal to 4, 3, 2, 1, 0. An overload allows a specific IEqualityComparer<T> to be used when comparing elements. The return value is just a Boolean, and is computed with immediate execution.

Table A.5. Sequence equality examples

Expression

Result

words.SequenceEqual
  (new[]{"zero","one",
         "two","three","four"})
True
words.SequenceEqual
  (new[]{"ZERO","ONE",
         "TWO","THREE","FOUR"})
False
words.SequenceEqual
  (new[]{"ZERO","ONE",
         "TWO","THREE","FOUR"},
  StringComparer.OrdinalIgnoreCase)
True

Generation

Out of all the generation operators (see table A.6), only one acts on an existing sequence: DefaultIfEmpty. This returns either the original sequence if it’s not empty, or a sequence with a single element otherwise. The element is normally the default value for the sequence type, but an overload allows you to specify which value to use.

Table A.6. Generation examples

Expression

Result

numbers.DefaultIfEmpty()
0, 1, 2, 3, 4
new int[0].DefaultIfEmpty()
0 (within an IEnumerable<int>)
new int[0].DefaultIfEmpty(10)
10 (within an IEnumerable<int>)
Enumerable.Range(15, 2)
15, 16
Enumerable.Repeat(25, 2)
25, 25
Enumerable.Empty<int>()
An empty IEnumerable<int>

There are three other generation operators that are just static methods in Enumerable:

  • Range generates a sequence of integers, with the parameters specifying the first value and how many values to generate.

  • Repeat generates a sequence of any type by repeating a specified single value for a specified number of times.

  • Empty generates an empty sequence of any type.

All of the generation operators use deferred execution.

Grouping

There are two grouping operators, but one of them is ToLookup (which we’ve already seen in A.3 as a conversion operator). That just leaves GroupBy, which we saw in section 11.6.1 when discussing the group ... by clause in query expressions. It uses deferred execution, but buffers results.

The result of GroupBy is a sequence of appropriately typed IGrouping elements. Each element has a key and a sequence of elements that match that key. In many ways, this is just a different way of looking at a lookup—instead of having random access to the groups by key, the groups are enumerated in turn. The order in which the groups are returned is the order in which their respective keys are discovered. Within a group, the order is the same as in the original sequence.

GroupBy (see table A.7) has a daunting number of overloads, allowing you to specify not only how a key is derived from an element (which is always required) but also optionally the following:

  • How to compare keys.

  • A projection from original element to the element within a group.

  • A projection from a key and an enumeration of elements to a result type. If this is specified, the result is just a sequence of elements of this result type.

Table A.7. GroupBy examples

Expression

Result

words.GroupBy(word => word.Length)
Key: 4; Sequence: "zero", "four"
Key: 3; Sequence: "one", "two"
Key: 5; Sequence: "three"
words.GroupBy
  (word => word.Length,   // Key
   word => word.ToUpper() // Group element
  )
Key: 4; Sequence: "ZERO", "FOUR"
Key: 3; Sequence: "ONE", "TWO"
Key: 5; Sequence: "THREE"

Frankly the last option is very confusing. I’d recommend avoiding it unless it definitely makes the code simpler for some reason.

Joins

Two operators are specified as join operators: Join and GroupJoin, both of which we saw in section 11.5 using join and join ... into query expression clauses respectively. Each method takes several parameters: two sequences, a key selector for each sequence, a projection to apply to each matching pair of elements, and optionally a key comparison.

For Join the projection takes one element from each sequence and produces a result; for GroupJoin the projection takes an element from the left sequence (in the chapter 11 terminology—the first one specified, usually as the sequence the extension method appears to be called on) and a sequence of matching elements from the right sequence. Both use deferred execution, and stream the left sequence but buffer the right sequence.

For the join examples in table A.8, we’ll match a sequence of names (Robin, Ruth, Bob, Emma) against a sequence of colors (Red, Blue, Beige, Green) by looking at the first character of both the name and the color, so Robin will join with Red and Bob will join with both Blue and Beige, for example.

Table A.8. Join examples

Expression

Result

names.Join // Left sequence
  (colors, // Right sequence
   name => name[0], // Left key selector
   color => color[0], // Right key selector
   // Projection for result pairs
   (name, color) => name+" - "+color
  )
"Robin - Red",
"Ruth - Red",
"Bob - Blue",
"Bob - Beige"
names.GroupJoin
  (colors,
   name => name[0],
   color => color[0],
   // Projection for key/sequence pairs
   (name, matches) => name+": "+
      string.Join("/", matches.ToArray())
  )
"Robin: Red",
"Ruth: Red",
"Bob: Blue/Beige",
"Emma: "

Note that Emma doesn’t match any of the colors—the name doesn’t appear at all in the results of the first example, but it does appear in the second, with an empty sequence of colors.

Partitioning

The partitioning operators either skip an initial part of the sequence, returning only the rest, or take only the initial part of a sequence, ignoring the rest. In each case you can either specify how many elements are in the first part of the sequence, or specify a condition—the first part of the sequence continues until the condition fails. After the condition fails for the first time, it isn’t tested again—it doesn’t matter whether later elements in the sequence match or not. All of the partitioning operators (see table A.9) use deferred execution.

Table A.9. Partitioning examples

Expression

Result

words.Take(3)

"zero", "one", "two"

words.Skip(3)

"three", "four"

words.TakeWhile(word => word[0] > 'k')

"zero", "one", "two", "three"

words.SkipWhile(word => word[0] > 'k')

"four"

Projection

We’ve seen both projection operators (Select and SelectMany) in chapter 11. Select is a simple one-to-one projection from element to result. SelectMany is used when there are multiple from clauses in a query expression: each element in the original sequence is used to generate a new sequence. Both projection operators (see table A.10) use deferred execution.

Table A.10. Projection examples

Expression

Result

words.Select(word => word.Length)
4, 3, 3, 5, 4
words.Select
  ((word, index) =>
   index.ToString()+": "+word)
"0: zero", "1: one", "2: two",
"3: three", "4: four"
words.SelectMany
  (word => word.ToCharArray())
'z', 'e', 'r', 'o', 'o', 'n', 'e', 't',
'w', 'o', 't', 'h', 'r', 'e', 'e', 'f',
'o', 'u', 'r'
words.SelectMany
  ((word, index) =>
   Enumerable.Repeat(word, index))
"one", "two", "two", "three",
"three", "three", "four", "four",
"four", "four"

There are overloads we didn’t see in chapter 11. Both methods have overloads that allow the index within the original sequence to be used within the projection, and SelectMany either flattens all of the generated sequences into a single sequence without including the original element at all, or uses a projection to generate a result element for each pair of elements. Multiple from clauses always use the overload that takes a projection. (Examples of this are quite long-winded, and not included here. See chapter 11 for more details.)

Quantifiers

The quantifier operators (see table A.11) all return a Boolean value, using immediate execution:

  • All checks whether all the elements in the sequence satisfy a specified condition.

  • Any checks whether any of the elements in the sequence satisfy a specified condition, or if no condition is specified, whether there are any elements at all.

  • Contains checks whether the sequence contains a particular element, optionally specifying a comparison to use.

Table A.11. Quantifier examples

Expression

Result

words.All(word => word.Length > 3)

false ("one" and "two" have exactly three letters)

words.All(word => word.Length > 2)

True

words.Any()

true (the sequence is not empty)

words.Any(word => word.Length == 6)

false (no six-letter words)

words.Any(word => word.Length == 5)

true ("three" satisfies the condition)

words.Contains("FOUR")

False

words.Contains("FOUR",
  StringComparer.OrdinalIgnoreCase)

True

Filtering

The two filtering operators are OfType and Where. For details and examples of the OfType operator, see the conversion operators section (A.3). The Where operator (see table A.12) has overloads so that the filter can take account of the element’s index. It’s unusual to require the index, and the where clause in query expressions doesn’t use this overload. Where always uses deferred execution.

Table A.12. Filtering examples

Expression

Result

words.Where(word => word.Length > 3)

words.Where
  ((word, index) =>
   index < word.Length)
"zero", "three", "four"

"zero",  //length=4, index=0
"one",   //length=3, index=1
"two",   //length=3, index=2
"three", //length=5, index=3
// Not "four", length=4, index=4

Set-based operations

It’s natural to be able to consider two sequences as sets of elements. The four set-based operators all have two overloads, one using the default equality comparison for the element type, and one where the comparison is specified in an extra parameter. All of them use deferred execution.

The Distinct operator is the simplest—it acts on a single sequence, and just returns a new sequence of all the distinct elements, discarding duplicates. The other operators also make sure they only return distinct values, but they act on two sequences:

  • Intersect returns elements that appear in both sequences.

  • Union returns the elements that are in either sequence.

  • Except returns the elements that are in the first sequence but not in the second. (Elements that are in the second sequence but not the first are not returned.)

For the examples of these operators in table A.13, we’ll use two new sequences: abbc ("a", "b", "b", "c") and cd ("c", "d").

Table A.13. Set-based examples

Expression

Result

abbc.Distinct()

"a", "b", "c"

abbc.Intersect(cd)

"c"

abbc.Union(cd)

"a", "b", "c", "d"

abbc.Except(cd)

"a", "b"

cd.Except(abbc)

"d"

Sorting

We’ve seen all the sorting operators before: OrderBy and OrderByDescending provide a “primary” ordering, while ThenBy and ThenByDescending provide subsequent orderings for elements that aren’t differentiated by the primary one. In each case a projection is specified from an element to its sorting key, and a comparison (between keys) can also be specified. Unlike some other sorting algorithms in the framework (such as List<T>.Sort), the LINQ orderings are stable—in other words, if two elements are regarded as equal in terms of their sorting key, they will be returned in the order they appeared in the original sequence.

The final sorting operator is Reverse, which simply reverses the order of the sequence. All of the sorting operators (see table A.14) use deferred execution, but buffer their data.

Table A.14. Sorting examples

Expression

Result

words.OrderBy(word => word)
"four", "one", "three", "two",
"zero"
// Order words by second character
words.OrderBy(word => word[1])
"zero", "three", "one", "four", 
"two"
// Order words by length;
// equal lengths returned in original
// order
words.OrderBy(word => word.Length)
"one", "two", "zero", "four", 
"three"
words.OrderByDescending
  (word => word.Length)
"three", "zero", "four", "one",
"two"
// Order words by length and then
// alphabetically
words.OrderBy(word => word.Length)
     .ThenBy(word => word)
"one", "two", "four", "zero",
"three"
// Order words by length and then
// alphabetically backwards
words.OrderBy(word => word.Length)
    .ThenByDescending(word => word)
"two", "one", "zero", "four",
"three"
words.Reverse()
"four", "three", "two", "one",
"zero"
..................Content has been hidden....................

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