Enumerating standard query operators

There are more than 50 query operators in the Enumerable class included in the System.Linq namespace. They are also known as standard query operators. Based on the function of the operators, we can divide them into several operations. Here, we are going to discuss all the query operators in LINQ provided by .NET Framework.

Filtering

Filtering is an operation that will evaluate the element of data so that only the element satisfying the condition will be selected. There are six filtering operators; they are Where, Take, Skip, TakeWhile, SkipWhile, and Distinct. As we know, we have already discussed the Where operator in our previous sample code, both in the fluent syntax and the query expression syntax, and have an idea that it will return a subset of elements satisfying a condition given by a predicate. Since we are clear enough about the Where operator, we can skip it and continue with the remaining five filtering operators.

The Take operator returns the first n elements and dumps the rest. In contrast, the Skip operator ignores the first n elements and returns the rest. Let's take a look at the following code from the FilteringOperation.csproj project:

public partial class Program 
{ 
  public static void SimplyTakeAndSkipOperator() 
  { 
    IEnumerable<int> queryTake = 
       intList.Take(10); 
    Console.WriteLine("Take operator"); 
    foreach (int i in queryTake) 
    { 
      Console.Write(String.Format("{0}	", i)); 
    } 
    Console.WriteLine(); 
    IEnumerable<int> querySkip = intList.Skip(10); 
    Console.WriteLine("Skip operator"); 
    foreach (int i in querySkip) 
    { 
      Console.Write(String.Format("{0}	", i)); 
    } 
    Console.WriteLine(); 
  } 
} 

We have two queries in the preceding code, queryTake, which applies the Take operator, and querySkip, which applies the Skip operator. They both consume intList, which is actually a list of integers containing the following data:

public partial class Program 
{ 
static List<int> intList = new List<int> 
  { 
    0,  1,  2,  3,  4, 
    5,  6,  7,  8,  9, 
    10, 11, 12, 13, 14, 
    15, 16, 17, 18, 19 
  }; 
} 

If we run the preceding SimplyTakeAndSkipOperator() method, we will get the following output:

Filtering

The preceding Take and Skip operator sample is simple code, since it deals with a collection containing only twenty elements. In fact, the Take and Skip operators are useful when we work with a huge collection, or maybe a database, to ease user access to the data. Suppose we have a million elements of the integer collection and we are looking for the element that is multiplied by two and seven. Without using the Take and Skip operators, we will have a ton of results, and if we show them on the console, they will clutter the console display. Let's take a look at the following code to prove this:

public partial class Program 
{ 
  public static void NoTakeSkipOperator() 
  { 
    IEnumerable<int> intCollection = 
       Enumerable.Range(1, 1000000); 
    IEnumerable<int> hugeQuery = 
        intCollection 
      .Where(h => h % 2 == 0 && h % 7 == 0); 
    foreach (int x in hugeQuery) 
    { 
      Console.WriteLine(x); 
    } 
  } 
} 

As you can see here, we have hugeQuery containing huge data. If we run the method, it needs about ten seconds to complete the iteration of all elements. We can also add the Count operator if we want to retrieve the actual elements hugeQuery contains, which is 71428 elements.

Now, we can modify the code by adding the Take and Skip operators around the foreach loop, as shown in the following code:

public partial class Program 
{ 
  public static void TakeAndSkipOperator() 
  { 
    IEnumerable<int> intCollection = 
       Enumerable.Range(1, 1000000); 
    IEnumerable<int> hugeQuery = 
       intCollection 
         .Where(h => h % 2 == 0 && h % 7 == 0); 
    int pageSize = 10; 
    for (int i = 0; i < hugeQuery.Count()/ pageSize; i++) 
    { 
      IEnumerable<int> paginationQuery =hugeQuery 
        .Skip(i * pageSize) 
        .Take(pageSize); 
      foreach (int x in paginationQuery) 
      { 
        Console.WriteLine(x); 
      } 
      Console.WriteLine( 
         "Press Enter to continue, " + 
           "other key will stop process!"); 
      if (Console.ReadKey().Key != ConsoleKey.Enter) 
        break; 
    } 
  } 
} 

In the preceding TakeAndSkipOperator() method, we add a couple of line of code in the highlighted lines. Now, although we have a lot of data, the output will be displayed conveniently when we run the method, as follows:

Filtering

As you can see, the entire result is not presented on the console, only ten integers each time. Users can press Enter key if they want to continue to read the rest of data. This is what we usually call pagination. The Take and Skip operators have done a good job to achieve it.

Besides discussing Take and Skip operators, we are going to discuss TakeWhile and SkipWhile operators in filtering operators. In TakeWhile operator, the input collection will be enumerated and each element will be sent to the query until the predicate is false. In contrast, the input collection will be enumerated, and when the predicate is true, the element will be sent to the query. Now, let's take a look at the following code to demonstrate the TakeWhile and SkipWhile operators:

public partial class Program 
{ 
  public static void TakeWhileAndSkipWhileOperators() 
  { 
    int[] intArray = { 10, 4, 27, 53, 2, 96, 48 }; 
    IEnumerable<int> queryTakeWhile = 
       intArray.TakeWhile(n => n < 50); 
    Console.WriteLine("TakeWhile operator"); 
    foreach (int i in queryTakeWhile) 
    { 
      Console.Write(String.Format("{0}	", i)); 
    } 
    Console.WriteLine(); 
    IEnumerable<int> querySkipWhile = 
       intArray.SkipWhile(n => n < 50); 
    Console.WriteLine("SkipWhile operator"); 
    foreach (int i in querySkipWhile) 
    { 
      Console.Write(String.Format("{0}	", i)); 
    } 
    Console.WriteLine(); 
  } 
} 

When we run the preceding method, we will get the following output on the console:

Filtering

Since we have n < 50 in the predicate, in TakeWhile, the enumeration will emit the elements until it reaches 53, and in SkipWhile, the element start to be emitted when the enumeration reaches 53.

We also have the Distinct operator in this filtering operation. The Distinct operator will return the input sequence without any duplicate elements. Suppose we have the following code:

public partial class Program 
{ 
  public static void DistinctOperator() 
  { 
    string words = "TheQuickBrownFoxJumpsOverTheLazyDog"; 
       IEnumerable <char> queryDistinct = words.Distinct(); 
    string distinctWords = ""; 
    foreach (char c in queryDistinct) 
    { 
      distinctWords += c.ToString(); 
    } 
    Console.WriteLine(distinctWords); 
  } 
} 

In the preceding code, we have a string and we intend to remove all duplicate letters in that string. We use the Distinct operator to get the query and then enumerate it. The result will be as follows:

Filtering

As you can see, there are some letters that have disappeared due to the use of the Distinct operator. There are no duplicate letters that appear in this case.

Projection

Projection is an operation that transforms an object into a new form. There are two projection operators; they are Select and SelectMany. Using the Select operator, we can transform each input element based on the given lambda expression, whereas using the SelectMany operator, we can transform each input element and then and flatten the resulting sequences into one sequence by concatenating them.

We had applied the Select operator when we discussed deferring LINQ execution. The following is the code snippet that uses the Select operator that we extract from the sample in Deferring LINQ execution topic:

IEnumerable<Member> memberQuery = 
  from m in memberList 
  where m.MemberSince.Year > 2014 
  orderby m.Name 
  select m; 

As you can see, we use the Select operator, which is the Select keyword in this case since we use the query expression syntax, to select all the resulting elements filtered by the Where keyword. As we know from the Select operator, the object can be transformed into another form, and we can transform that element typed Member class object into the element typed RecentMember class object using the following code:

IEnumerable<RecentMember> memberQuery = 
  from m in memberList 
  where m.MemberSince.Year > 2014 
  orderby m.Name 
  select new RecentMember{ 
    FirstName = m.Name.GetFirstName(), 
    LastName = m.Name.GetLastName(), 
    Gender = m.Gender, 
    MemberSince = m.MemberSince, 
    Status = "Valid" 
}; 

Using the preceding code, we assume that there is a class named RecentMember, as follows:

public class RecentMember 
{ 
  public string FirstName { get; set; } 
  public string LastName { get; set; } 
  public string Gender { get; set; } 
  public DateTime MemberSince { get; set; } 
  public string Status { get; set; } 
} 

From the preceding code snippet, we can see that we transform each input element using the Select operator. We can insert the code snippet into the following complete source:

public partial class Program 
{ 
  public static void SelectOperator() 
  { 
    List<Member> memberList = new List<Member>() 
    { 
      new Member 
      { 
        ID = 1, 
        Name = "Eddie Morgan", 
        Gender = "Male", 
        MemberSince = new DateTime(2016, 2, 10) 
      }, 
      new Member 
      { 
        ID = 2, 
        Name = "Millie Duncan", 
        Gender = "Female", 
        MemberSince = new DateTime(2015, 4, 3) 
      }, 
      new Member 
      { 
        ID = 3, 
        Name = "Thiago Hubbard", 
        Gender = "Male", 
        MemberSince = new DateTime(2014, 1, 8) 
      }, 
      new Member 
      { 
        ID = 4, 
        Name = "Emilia Shaw", 
        Gender = "Female", 
        MemberSince = new DateTime(2015, 11, 15) 
      } 
    }; 
    IEnumerable<RecentMember> memberQuery = 
      from m in memberList 
      where m.MemberSince.Year > 2014 
      orderby m.Name 
      select new RecentMember{ 
        FirstName = m.Name.GetFirstName(), 
        LastName = m.Name.GetLastName(), 
        Gender = m.Gender, 
        MemberSince = m.MemberSince, 
        Status = "Valid" 
      }; 
    foreach (RecentMember rm in memberQuery) 
    { 
      Console.WriteLine( 
         "First Name  : " + rm.FirstName); 
      Console.WriteLine( 
         "Last Name   : " + rm.LastName); 
      Console.WriteLine( 
         "Gender      : " + rm.Gender); 
      Console.WriteLine 
         ("Member Since: " + rm.MemberSince.ToString("dd/MM/yyyy")); 
      Console.WriteLine( 
         "Status      : " + rm.Status); 
      Console.WriteLine(); 
    } 
  } 
} 

Since we have enumerated the query using the foreach iterator and have written the element to the console using the Console.WriteLine() method, after running the preceding SelectOperator() method, we will get the following output on the console:

Projection

From the preceding console screenshot, we can see that we have successfully transformed the Member type input elements into the RecentMember type output elements. We can also use the fluent syntax to produce the exact same result, as shown in the following code snippet:

IEnumerable<RecentMember> memberQuery = 
   memberList 
  .Where(m => m.MemberSince.Year > 2014) 
  .OrderBy(m => m.Name) 
  .Select(m => new RecentMember 
{ 
  FirstName = m.Name.GetFirstName(), 
  LastName = m.Name.GetLastName(), 
  Gender = m.Gender, 
  MemberSince = m.MemberSince, 
  Status = "Valid" 
}); 

Now, let's move on to the SelectMany operator. Using this operator, we can select more than one sequence and then flatten the result into one sequence. Suppose we have two collections and we are going to select all of them; we can achieve the goal using the following code:

public partial class Program 
{ 
  public static void SelectManyOperator() 
  { 
    List<string> numberTypes = new List<string>() 
    { 
      "Multiplied by 2", 
      "Multiplied by 3" 
    }; 
    List<int> numbers = new List<int>() 
    { 
      6, 12, 18, 24 
    }; 
    IEnumerable<NumberType> query = 
       numbers.SelectMany( 
          num => numberTypes,  
          (n, t) =>new NumberType 
          { 
            TheNumber = n, 
            TheType = t 
          }); 
    foreach (NumberType nt in query) 
    { 
      Console.WriteLine(String.Format( 
         "Number: {0,2} - Types: {1}", 
           nt.TheNumber, 
             nt.TheType)); 
    } 
  } 
} 

As you can see, we have two collections named numberTypes and numbers and want to take any possible combination from their elements. The result is in a new form typed NumberType with the following definition:

public class NumberType 
{ 
  public int TheNumber { get; set; } 
  public string TheType { get; set; } 
} 

If we run the preceding SelectManyOperator() method, the following output will be displayed on the console:

Projection

In this code, we actually iterate the two collections to construct the combination of two collections since the implementation of the SelectMany operator is as follows:

public static IEnumerable<TResult> SelectMany<TSource, TResult>( 
  this IEnumerable<TSource> source, 
  Func<TSource, IEnumerable<TResult>> selector) 
{ 
  foreach (TSource element in source) 
  foreach (TResult subElement in selector (element)) 
  yield return subElement; 
} 

We can also apply the query expression syntax to replace the preceding fluent syntax using the following code snippet:

IEnumerable<NumberType> query = 
  from n in numbers 
  from t in numberTypes 
  select new NumberType 
{ 
  TheNumber = n, 
  TheType = t 
}; 

The output from the query expression syntax will be exactly the same as the fluent syntax.

Note

The from keyword has two different meanings in the query expression syntax. When we use the keyword at the start of the syntax, it will introduce the original range variable and the input sequence. When we use the keyword anywhere other than at the beginning, it will be translated into the SelectMany operator.

Joining

Joining is an operation that meshes different source sequences with no direct object model relationship into a single output sequence. Nevertheless, the elements in each source have to share a value that can be compared for equality. There are two joining operators in LINQ; they are Join and GroupJoin.

The Join operator uses a lookup technique to match elements from two sequences and then returns a flat result set. To explain this further, let's take a look at the following code, which we can find in the Joining.csproj project:

public partial class Program 
{ 
  public static void JoinOperator() 
  { 
    Course hci = new Course{ 
      Title = "Human Computer Interaction", 
      CreditHours = 3}; 
    Course iis = new Course{ 
      Title = "Information in Society", 
      CreditHours = 2}; 
    Course modr = new Course{ 
      Title = "Management of Digital Records", 
      CreditHours = 3}; 
    Course micd = new Course{ 
      Title = "Moving Image Collection Development", 
      CreditHours = 2}; 
    Student carol = new Student{ 
      Name = "Carol Burks", 
      CourseTaken = modr}; 
    Student river = new Student{ 
      Name = "River Downs", 
      CourseTaken = micd}; 
    Student raylee = new Student{ 
      Name = "Raylee Price", 
      CourseTaken = hci}; 
    Student jordan = new Student{ 
      Name = "Jordan Owen", 
      CourseTaken = modr}; 
    Student denny = new Student{ 
      Name = "Denny Edwards", 
      CourseTaken = hci}; 
    Student hayden = new Student{ 
      Name = "Hayden Winters", 
      CourseTaken = iis}; 
    List<Course> courses = new List<Course>{
      hci, iis, modr, micd};
    List<Student> students = new List<Student>{
      carol, river, raylee, jordan, denny, hayden}; 
    var query = courses.Join( 
      students, 
      course => course, 
      student => student.CourseTaken, 
      (course, student) => 
        new {StudentName = student.Name, 
          CourseTaken = course.Title }); 
    foreach (var item in query) 
    { 
      Console.WriteLine( 
        "{0} - {1}", 
        item.StudentName, 
        item.CourseTaken); 
    } 
  } 
} 

The preceding code consumes Student and Course classes with the following implementation:

public class Student 
{ 
  public string Name { get; set; } 
  public Course CourseTaken { get; set; } 
} 
public class Course 
{ 
  public string Title { get; set; } 
  public int CreditHours { get; set; } 
} 

If we run the preceding JoinOperator() method, we will get the following output on the console:

Joining

From the preceding code, we can see that we have two sequences, which are courses and students. We can join these two sequences using the Join operator and then we create an anonymous type as the result. We can also use the query expression syntax to join these two sequences. The following is the code snippet we have to replace in our previous query creation:

var query = 
from c in courses 
join s in students on c.Title equals s.CourseTaken.Title 
select new { 
  StudentName = s.Name, 
  CourseTaken = c.Title }; 

If we run the JoinOperator() method again, we will get the exact same output on the console.

The GroupJoin operator uses the same technique that the Join operator uses, but it returns a hierarchical result set. Let's take a look at the following code that explains the GroupJoin operator:

public partial class Program 
{ 
  public static void GroupJoinOperator() 
  { 
    Course hci = new Course{ 
      Title = "Human Computer Interaction", 
      CreditHours = 3}; 
 
    Course iis = new Course{ 
      Title = "Information in Society", 
      CreditHours = 2}; 
 
    Course modr = new Course{ 
      Title = "Management of Digital Records", 
      CreditHours = 3}; 
 
    Course micd = new Course{ 
      Title = "Moving Image Collection Development", 
      CreditHours = 2}; 
 
    Student carol = new Student{ 
      Name = "Carol Burks", 
      CourseTaken = modr}; 
 
    Student river = new Student{ 
      Name = "River Downs", 
      CourseTaken = micd}; 
 
    Student raylee = new Student{ 
      Name = "Raylee Price", 
      CourseTaken = hci}; 
 
    Student jordan = new Student{ 
      Name = "Jordan Owen", 
      CourseTaken = modr}; 
 
    Student denny = new Student{ 
      Name = "Denny Edwards", 
      CourseTaken = hci}; 
 
    Student hayden = new Student{ 
      Name = "Hayden Winters", 
      CourseTaken = iis}; 
 
    List<Course> courses = new List<Course>{ 
      hci, iis, modr, micd}; 
 
    List<Student> students = new List<Student>{ 
      carol, river, raylee, jordan, denny, hayden}; 
 
    var query = courses.GroupJoin( 
      students, 
      course => course, 
      student => student.CourseTaken, 
      (course, studentCollection) => 
      new{ 
        CourseTaken = course.Title, 
        Students =  
        studentCollection 
        .Select(student => student.Name) 
      }); 
 
      foreach (var item in query) 
      { 
        Console.WriteLine("{0}:", item.CourseTaken); 
        foreach (string stdnt in item.Students) 
        { 
          Console.WriteLine("  {0}", stdnt); 
        } 
      } 
    } 
} 

The preceding code is similar to the Join operator code that we discussed earlier. The difference is in the way we create the query. In the GroupJoin operator, we join the two sequences into another sequence with a key. Let's invoke the preceding GroupJoinOperator() method, and we will get the following output on the console:

Joining

As you can see in the output, we group all the students who take a particular course. We then enumerate the query to get the result.

Ordering

Ordering is an operation that will sort the return sequence from the input sequence using the default comparer. For instance, if we have a sequence in the string type, the default comparer will perform an alphabetical sort from A to Z. Let's take a look at the following code, which we can find in the Ordering.csproj project:

public partial class Program 
{ 
  public static void OrderByOperator() 
  { 
    IEnumerable<string> query = 
      nameList.OrderBy(n => n); 
 
    foreach (string s in query) 
    { 
      Console.WriteLine(s); 
    } 
  } 
} 

For the sequence that we have to feed to the query, the code is as follows:

public partial class Program 
{ 
  static List<string> nameList = new List<string>() 
  { 
    "Blair", "Lane", "Jessie", "Aiden", 
    "Reggie", "Tanner", "Maddox", "Kerry" 
  }; 
} 

If we run the preceding OrderByOperator() method, we will get the following output on the console:

Ordering

As you can see, we execute the ordering operation using the default comparer, so the sequence is sorted alphabetically. We can also use the query expression syntax to replace the following code snippet:

IEnumerable<string> query = 
  nameList.OrderBy(n => n); 

The query expression syntax we have for the sequence is shown in the following code snippet:

IEnumerable<string> query = 
  from n in nameList 
  orderby n 
  select n; 

We can create our own comparer as the key selector to sort the sequence by the last character in each element; here is the code we can use to achieve this using the IComparer<T> interface. Suppose we want to sort our previous sequence:

public partial class Program 
{ 
  public static void OrderByOperatorWithComparer() 
  { 
    IEnumerable<string> query = 
      nameList.OrderBy( 
       n => n,  
      new LastCharacterComparer()); 
    foreach (string s in query) 
    { 
      Console.WriteLine(s); 
    } 
  } 
} 

We also create a new class, LastCharacterComparer, which inherits the IComparer<string> interface, as follows:

public class LastCharacterComparer : IComparer<string> 
{ 
  public int Compare(string x, string y) 
  { 
    return string.Compare( 
     x[x.Length - 1].ToString(), 
      y[y.Length - 1].ToString()); 
  } 
} 

We will get the following output on the console when we run the preceding OrderByOperatorWithComparer() method:

Ordering

As you can see, we have an ordered sequence now, but the sorting key is the last character of each element. This is achieved with the help of our custom comparer. Unfortunately, the custom comparer is only available using the fluent syntax. In other words, we can't use it in the query expression method.

When we sort the sequence, we can have more than one comparer as a condition. We can use the ThenBy extension method for the second condition after we call the OrderBy method. Let's take a look at the following code to demonstrate this:

public partial class Program 
{ 
  public static void OrderByThenByOperator() 
  { 
    IEnumerable<string> query = nameList 
      .OrderBy(n => n.Length) 
      .ThenBy(n => n); 
    foreach (string s in query) 
    { 
      Console.WriteLine(s); 
    } 
  } 
} 

From the preceding code, we sort the sequence by the length of each element, and then we sort the result alphabetically. If we call the OrderByThenByOperator() method, we will get the following output:

Ordering

We can also use query expression syntax when we need to sort a sequence using two conditions, as shown in the following code snippet:

IEnumerable<string> query = 
  from n in nameList 
  orderby n.Length, n 
  select n; 

If we run OrderByThenByOperator() method again after replacing the query operation with the query expression syntax, we will get the exact same output as we get when we use the fluent syntax. However, there is no ThenBy keyword in the query expression syntax. What we need to do is just separate the condition using a comma.

We can use our custom comparer in the use of the ThenBy method as well. Let's take a look at the following code to try this:

public partial class Program 
{ 
  public static void OrderByThenByOperatorWithComparer() 
  { 
    IEnumerable<string> query = nameList 
      .OrderBy(n => n.Length) 
      .ThenBy(n => n, new LastCharacterComparer()); 
    foreach (string s in query) 
    { 
      Console.WriteLine(s); 
    } 
  } 
} 

In this code, we use the same LastCharacterComparer class that we use in the OrderByOperatorWithComparer() method. If we call the OrderByThenByOperatorWithComparer() method, the following is the output we will get on the console:

Ordering

Besides ascending sorting, we also have descending sorting. In fluent syntax, we can simply use OrderByDescending() and ThenByDescending() methods. The usage in code is exactly the same, as the code for sorting in an ascending order. However, in the query expression syntax, we have the descending keyword to achieve this goal. We use this keyword just after we define the condition in the orderby keyword, as shown in the following code:

public partial class Program 
{ 
  public static void OrderByDescendingOperator() 
  { 
    IEnumerable<string> query = 
      from n in nameList 
      orderby n descending 
      select n; 
    foreach (string s in query) 
    { 
      Console.WriteLine(s); 
    } 
  } 
} 

As you can see, there is a descending keyword in the as well code. Actually, we can replace the descending keyword with the ascending keyword in order to sort the sequence in an ascending manner. However, ascending sorting is the default sorting in LINQ, so the ascending keyword can be omitted. The following is the output if we run the code and invoke the OrderByDescendingOperator() method:

Ordering

Grouping

Grouping is an operation that will generate a sequence of IGrouping<TKey, TElement> objects, which are grouped by the TKey key value. For instance, we will group a sequence of path address files in one directory by their first letters. The following code can be found in the Grouping.csproj project file and will search all file in G:packages, which is the setup files of Visual Studio 2015 Community Edition. You can adjust the drive letter and folder name based on the drive letter and folder name on your computer.

public partial class Program 
{ 
  public static void GroupingByFileNameExtension() 
  { 
    IEnumerable<string> fileList =  
      Directory.EnumerateFiles( 
        @"G:packages", "*.*",  
        SearchOption.AllDirectories); 
    IEnumerable<IGrouping<string, string>> query = 
      fileList.GroupBy(f => 
      Path.GetFileName(f)[0].ToString()); 
    foreach (IGrouping<string, string> g in query) 
    { 
      Console.WriteLine(); 
      Console.WriteLine( 
         "File start with the letter: " +  
           g.Key); 
      foreach (string filename in g) 
      Console.WriteLine( 
         "..." + Path.GetFileName(filename)); 
     } 
  } 
} 

The preceding code will find all files in the G:packages folder (including all the subdirectories) then group them based on the first letter in their filenames. As you can see when we enumerate a query using the foreach loop, we have g.Key, which is the key selector for grouping that string list. If we run the GroupingByFileNameExtension() method, we will get the following output on the console:

Grouping

The GroupBy extension method also has a clause in order to be used in the query expression syntax. The clauses we can use are group and by. The following code snippet can replace the query in our previous code:

IEnumerable<IGrouping<string, string>> query = 
  from f in fileList 
  group f by Path.GetFileName(f)[0].ToString(); 

We will still have the same output as the fluent syntax output, although we replace the query using the query expression syntax. As you can see, the grouping operation in LINQ only groups the sequence; it does not sort. We can sort the result using the OrderBy operator provided by LINQ.

In the preceding query expression syntax, we see that we do not need the select clause again since the group clause will end the query as well. However, we still need the select clause when using the group clause if we add a query continuation clause. Now let's we take a look at the following code, which applies the query continuation clause to sort the sequence:

public partial class Program 
{ 
  public static void GroupingByInto() 
  { 
    IEnumerable<string> fileList = 
      Directory.EnumerateFiles( 
        @"G:packages", "*.*", 
        SearchOption.AllDirectories); 
    IEnumerable<IGrouping<string, string>> query = 
      from f in fileList 
      group f  
        by Path.GetFileName(f)[0].ToString() 
        into g 
      orderby g.Key 
      select g; 
    foreach (IGrouping<string, string> g in query) 
    { 
      Console.WriteLine( 
        "File start with the letter: " + g.Key); 
      //foreach (string filename in g) 
      Console.WriteLine(           "..." + Path.GetFileName(filename)); 
    } 
  } 
} 

As you can see in the preceding code, we modify the query by adding the query continuation clause and the orderby operator to sort the sequence result. The query continuation clause we use is the into keyword. Using the into keyword, we store the grouping result, and then we manipulate the grouping again. If we run the preceding code, we will get the following output on the console:

Grouping

We deliberately remove the elements of each group since what we want to examine now is the key itself. Now, we can see that the key is in the ascending order. This happens since we first store the result of the grouping then we sort the key in an ascending manner.

The set operation

The set operation is an operation that returns a result set that is based on the presence or the absence of equivalent elements within the same or separate collection. There are four set operators that LINQ provides; they are Concat, Union, Intersect, and Except. For all the four set operators, there is no query expression keyword.

Let's start with Concat and Union. Using the Concat operator, we will get all the elements of first sequence followed by all the elements of the second sequence as a result. Union does this with the Concat operator but returns only one element for the duplicate elements. The following code, which we can find in the SetOperation.csproj project, demonstrates the difference between Concat and Union:

public partial class Program 
{ 
  public static void ConcatUnionOperator() 
  { 
    IEnumerable<int> concat = sequence1.Concat(sequence2); 
    IEnumerable<int> union = sequence1.Union(sequence2); 
    Console.WriteLine("Concat"); 
    foreach (int i in concat) 
    { 
      Console.Write(".." + i); 
    } 
    Console.WriteLine(); 
    Console.WriteLine(); 
    Console.WriteLine("Union"); 
    foreach (int i in union) 
    { 
      Console.Write(".." + i); 
    } 
    Console.WriteLine(); 
    Console.WriteLine(); 
  } 
} 

The two sequences we have as as follows:

public partial class Program 
{ 
  static int[] sequence1 = { 1, 2, 3, 4, 5, 6 }; 
  static int[] sequence2 = { 3, 4, 5, 6, 7, 8 }; 
} 

Our preceding code tries to use the Concat and Union operators. And as per our discussion, the following is the output we will get if we run the ConcatUnionOperator() method:

The set operation

The Intersect and Except are set operators as well. Intersect returns the elements that are present in both of the input sequences. Except returns the elements of the first input sequence, which are not present in the second. The following code explains the difference between Intersect and Except:

public partial class Program 
{ 
  public static void IntersectExceptOperator() 
  { 
    IEnumerable<int> intersect = sequence1.Intersect(sequence2); 
    IEnumerable<int> except1 = sequence1.Except(sequence2); 
    IEnumerable<int> except2 = sequence2.Except(sequence1); 
    Console.WriteLine("Intersect of Sequence"); 
    foreach (int i in intersect) 
    { 
      Console.Write(".." + i); 
    } 
    Console.WriteLine(); 
    Console.WriteLine(); 
    Console.WriteLine("Except1"); 
    foreach (int i in except1) 
    { 
      Console.Write(".." + i); 
    } 
    Console.WriteLine(); 
    Console.WriteLine(); 
    Console.WriteLine("Except2"); 
    foreach (int i in except2) 
    { 
      Console.Write(".." + i); 
    } 
    Console.WriteLine(); 
    Console.WriteLine(); 
  } 
} 

If we invoke the IntersectExceptOperator() method, the following output will be displayed on the console screen:

The set operation

We apply the two sequences we used earlier in the ConcatUnionOperator() method as an input. As you can see from the preceding console screenshot, in the Intersect operation, only duplication elements are returned. In the Except operation, only the unique element will be returned.

Conversion methods

The main role of conversion methods is to convert one type of collections to other types of collection. Here, we will discuss the conversion methods provided by LINQ; they are OfType, Cast, ToArray, ToList, ToDictionary, and ToLookup.

The OfType and Cast methods have a similar function; they convert IEnumerable into IEnumerable<T>. The difference is that OfType will discard the wrong type elements, if any, and Cast will throw an exception if there is any wrong type element. Let's take a look at the following code, which we can find in the ConversionMethods.csproj project:

public partial class Program 
{ 
  public static void OfTypeCastSimple() 
  { 
    ArrayList arrayList = new ArrayList(); 
    arrayList.AddRange(new int[] { 1, 2, 3, 4, 5 }); 
 
    IEnumerable<int> sequenceOfType = arrayList.OfType<int>(); 
    IEnumerable<int> sequenceCast = arrayList.Cast<int>(); 
 
    Console.WriteLine( 
      "OfType of arrayList"); 
    foreach (int i in sequenceOfType) 
    { 
      Console.Write(".." + i); 
    } 
    Console.WriteLine(); 
    Console.WriteLine(); 
 
    Console.WriteLine( 
      "Cast of arrayList"); 
    foreach (int i in sequenceCast) 
    { 
      Console.Write(".." + i); 
    } 
    Console.WriteLine(); 
    Console.WriteLine(); 
  } 
} 

The preceding code is a simple example of using the OfType and Cast conversions. We have an array that contains only int elements. Indeed, they can be converted easily. The following will be the output if we run the OfTypeCastSimple() method:

Conversion methods

Note

In .NET Core, ArrayList definition lies in System.Collections.NonGeneric.dll. Hence, we have to download the NuGet package on https://www.nuget.org/packages/System.Collections.NonGeneric/

Now let's add several lines of code to the preceding code. The code will now be as follows:

public partial class Program 
{ 
  public static void OfTypeCastComplex() 
  { 
    ArrayList arrayList = new ArrayList(); 
    arrayList.AddRange( 
      new int[] { 1, 2, 3, 4, 5 }); 
 
    arrayList.AddRange( 
       new string[] {"Cooper", "Shawna", "Max"}); 
    IEnumerable<int> sequenceOfType = 
       arrayList.OfType<int>(); 
    IEnumerable<int> sequenceCast = 
       arrayList.Cast<int>(); 
 
    Console.WriteLine( 
      "OfType of arrayList"); 
    foreach (int i in sequenceOfType) 
    { 
      Console.Write(".." + i); 
    } 
    Console.WriteLine(); 
    Console.WriteLine(); 
 
    Console.WriteLine( 
       "Cast of arrayList"); 
    foreach (int i in sequenceCast) 
    { 
      Console.Write(".." + i); 
    } 
    Console.WriteLine(); 
    Console.WriteLine(); 
  } 
} 

From the preceding code, we can see that we changed the method name to OfTypeCastComplex and inserted the code to add a string element to arrayList. If we run the method, the OfType conversion will run successfully and return only the int element, while the Cast conversion will throw an exception since there are some string elements in the input sequence.

The others conversion methods are ToArray() and ToList(). The difference between them is that ToArray() will convert the sequence into an array and ToList() into a generic list. Also, ToDictionary() and ToLookup() method names are available for conversion. ToDictionary() will create Dictionary<TKey, TValue> from the sequence based on a specified key selector function, and ToLookup() will create Lookup<TKey, TElement> from the sequence based on the specified key selector and element selector functions.

Element operation

Element operation is an operation that extracts individual elements from the sequence according to their index or using a predicate. There are several element operators that exist in LINQ; they are First, FirstOrDefault, Last, Single, SingleOrDefault, ElementAt, and DefaultIfEmpty. Let's use the sample code to understand the function of all these element operators.

The following is the code to demonstrate the element operator, which we can find in the ElementOperation.csproj project:

public partial class Program 
{ 
  public static void FirstLastOperator() 
  { 
    Console.WriteLine( 
      "First Operator: {0}", 
      numbers.First()); 
    Console.WriteLine( 
      "First Operator with predicate: {0}", 
      numbers.First(n => n % 3 == 0)); 
    Console.WriteLine( 
      "Last Operator: {0}", 
      numbers.Last()); 
    Console.WriteLine( 
      "Last Operator with predicate: {0}", 
      numbers.Last(n => n % 4 == 0)); 
  } 
} 

The preceding code demonstrates the use of the First and Last operators. The numbers array is as follows:

public partial class Program 
{ 
  public static int[] numbers = { 
    1, 2, 3, 
    4, 5, 6, 
    7, 8, 9 
  }; 
} 

Before we move further, let's take a moment to look at the following output on the console if we run the FirstLastOperator() method:

Element operation

From the output, we can find that the First operator will return the first element of the sequence, and the Last operator will return the last element. We can also use a lambda expression for the First and Last operators to filter the sequence. In the preceding example, we filtered the sequence to numbers that can be divided only by four.

Unfortunately, the First and Last operators cannot return an empty value; instead, they throw an exception. Let's examine the following code regarding the use of the First operator, which will return an empty sequence:

public partial class Program 
{ 
  public static void FirstOrDefaultOperator() 
  { 
    Console.WriteLine( 
      "First Operator with predicate: {0}", 
      numbers.First(n => n % 10 == 0)); 
    Console.WriteLine( 
      "First Operator with predicate: {0}", 
      numbers.FirstOrDefault(n => n % 10 == 0)); 
  } 
} 

If we uncomment all commented code lines in the preceding code, the method will throw an exception since there's no number that can be divided by 10. To solve this problem, we can use the FirstOrDefault operator instead, and it will return the default value because the numbers are in the sequence of integers. So, it will return the default value of the integer, which is 0.

We also have Single and SingleOrDefault as element operators, and we can take a look at their use in the following code:

public partial class Program 
{ 
  public static void SingleOperator() 
  { 
    Console.WriteLine( 
      "Single Operator for number can be divided by 7: {0}", 
      numbers.Single(n => n % 7 == 0)); 
    Console.WriteLine( 
      "Single Operator for number can be divided by 2: {0}", 
      numbers.Single(n => n % 2 == 0)); 
 
    Console.WriteLine( 
      "SingleOrDefault Operator: {0}", 
      numbers.SingleOrDefault(n => n % 10 == 0)); 
 
    Console.WriteLine( 
      "SingleOrDefault Operator: {0}", 
      numbers.SingleOrDefault(n => n % 3 == 0)); 
  } 
} 

If we run the preceding code, an exception is thrown due to the following code snippet:

Console.WriteLine( 
  "Single Operator for number can be divided by 2: {0}", 
  numbers.Single(n => n % 2 == 0)); 

Also, the following code snippet causes an error:

Console.WriteLine( 
  "SingleOrDefault Operator: {0}", 
  numbers.SingleOrDefault(n => n % 3 == 0)); 

The error occurs because the Single operator can only have one matching element. In the first code snippet, we have 2, 4, 6, and 8 as the result. In the second code snippet, we have 3, 6, and 9 as the result.

The Element operation also has ElementAt and ElementAtOrDefault operators to get the nth element from the sequence. Let's take a look at the following code to demonstrate the operators:

public partial class Program 
{ 
  public static void ElementAtOperator() 
  { 
    Console.WriteLine( 
      "ElementAt Operator: {0}", 
      numbers.ElementAt(5)); 
 
    //Console.WriteLine( 
      //"ElementAt Operator: {0}", 
      //numbers.ElementAt(11)); 
 
    Console.WriteLine( 
      "ElementAtOrDefault Operator: {0}", 
      numbers.ElementAtOrDefault(11)); 
  } 
} 

Like the First and Last operators, ElementAt has to return the value as well. The commented code lines in the preceding code will throw an exception since there's no element in index 11. However, we can overcome this problem using ElementAtOrDefault, and then the commented lines will return the default value of int.

The last in element operation is the DefaultIfEmpty operator, which will return the default value in a sequence if no element is found in the input sequence. The following code will demonstrate the DefaultIfEmpty operator:

public partial class Program 
{ 
  public static void DefaultIfEmptyOperator() 
  { 
    List<int> numbers = new List<int>(); 
 
    //Console.WriteLine( 
      //"DefaultIfEmpty Operator: {0}", 
      //numbers.DefaultIfEmpty()); 
 
    foreach (int number in numbers.DefaultIfEmpty()) 
    { 
      Console.WriteLine( 
        "DefaultIfEmpty Operator: {0}", number); 
    } 
  } 
} 

Since the return of the DefaultIfEmpty operator is IEnumerable<T>, we have to enumerate it, although it contains only one element. As you can see in the preceding code, we comment the direct access of the numbers variable because it will return the type of variable, not the value of the variable. Instead, we have to enumerate the numbers query to get the one and only value stored in the IEnumerable<T> variable.

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

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