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 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:
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:
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:
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:
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 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:
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:
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.
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 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:
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:
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 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:
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:
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:
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:
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:
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:
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:
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 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 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:
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.
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:
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 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:
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.