Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

C# Lectures - Lecture 10: LINQ introduction, LINQ to 0bjects Part 1

5.00/5 (19 votes)
19 Jul 2016CPOL18 min read 27.6K   530  
10th article from my series. We will talk about LINQ in general and LINQ to objects deferred operators

Full Lectures Set

Introduction

Modern world is built around data. Data is everything. In comparison to few dozen years ago every person in the world receives much more information daily. Mostly this information comes to him from something that finally is an application. No meter you read news from browser, listen radio or watch TV with the big probability they were formed initially as text in Word application and saved to file or DB. Data, data, data everything about data and 99% of it is handled by software that was created in the past, in progress of creation now or will be created in the future. What I'm trying to say here is that the purpose of modern software mostly is to handle data. This data may come to your application in different format and in this article I will focus on text data handling. By text data I assume data that is saved, translated and handled as set of symbols. This might be not necessary text that we can read, this can be numbers, date and time values, etc.

Text data may arrive to your application from different sources and in different format as well. You can receive data via mail application, via network protocol from another application, read it from file or data base. There are many non-standard ways to transfer and handle text data and there are some standards to store and transfer text data. For example mostly data is transferred as JSON or XML and saved in relational or non-relational DB. There are many request for programmer to read, write and especially search data during application runtime. There were created thousands of private and public libraries and classes to help organize work with data easier. Microsoft development team decided to make an impact in this area as well and create abstracted built into language mechanism to query data of: arrays, collections, data bases, XML documents, etc. They created LINQ and I will talk about it in this article.

LINQ what is it?

Let's start from name LINQ is an abbreviation for Microsoft's Language Integrated Query. As you can understand from the name LINQ is about queries. Every query return set of objects or specific object that match conditions. This returned set of data is called sequence in LINQ. Almost all sequences in LINQ are of type IEnumerable<T>. You don't need to install something to work with LINQ in your C# application. LINQ is integrated to .NET from version 3.5 and Visual Studio 2008.

In general LINQ can be divided to following areas:

  1. LINQ to Objects - allows to perform queries against data collections in memory and arrays
  2. LINQ to XML - name of the API part designed to work with XML
  1. LINQ to DataSet - API that are designed to work with DataSets
  1. LINQ to SQL - part of the API allowing you to do queries to Microsoft SQL data base
  1. LINQ to Entities - it is an alternative way to interact with data base using Entity framework

IEnumerable<T>, Sequence, Standard Query Operators and yielding

The cool thing of LINQ is that it is fully integrated with C# and can be used with the same containers and arrays that you used before LINQ usage. The functionality of LINQ is accomplished with IEnumerable<T> interface. This interface is implemented by all C# generic collections and arrays. With this interface enumeration of collections is done.

When you execute some method on top of your collection and got result as IEnumerable<T> this is called sequence. Sequence is another important term in LINQ. If you have variable declared as IEnumerable<T> this is called as sequence of T. For example IEnumerable<string> is a sequence of strings.

Besides IEnumerable and sequence, there is third important thing in LINQ these are Standard Query Operators. Most of them are defined in System.Linq.Enumerable and designed to get IEnumerable<T> as first argument. As Query Operators are extension methods it is better to call them on variable of type IEnumerable<T> then to pass this variable as first argument. When you combine different operators, you may have very complex queries. Most of Standard Query Operators return IEnumerable<T> queries. There are few dozens of Standard Query Operators. You can see all of the in MSDN by following link. We will review their usage in this and next articles.

Yielding and Deferred queries

 It is important to know that operator is performed only when resulting query is enumerated. This is called yielding. As query itself is executed only when specific element is enumerated, it may potentially contain errors that you can't catch at compile time. Example below demonstrates this issue:

C#
string[] CarBrands = { "Mersedes", "Ford", "Lexus", "Toyota",
                       "Honda", "Hyunday", "BMW", "KIA", "Chevrolet",
                       "Tesla", "Lamborghini", "Ferrari", "Lincoln",
                        "Cadillac"};
//----------------yielding----------------------
Console.WriteLine("-----------------YIELDING--------------------");
IEnumerable<string> cars = CarBrands.Where(s => s.StartsWith("T"));
//result for below is Toyota and Tesla
foreach (string car in cars)
{
    Console.WriteLine(car);
}
//the query below is problematic and has exception out of range for values of "BMW" and "KIA"
try
{
    cars = CarBrands.Where(s => s[3]!='D');
    foreach (string car in cars)
    {
        Console.WriteLine(car);
    }
}
catch (IndexOutOfRangeException ex)
{
    Console.WriteLine("Out of range catched");
}

As you can see even so query is compiled it may contain the bug. You should be very careful with this and make sure you test your code and handle all potential errors that may happen with your queries on different data sets.

Another potential trick with the fact that query is executed only when specific object is referenced is that if same query is executed twice and you change the original data set in a middle, then result of these queries will be different. Sample below demonstrates the fact that queries are deferred:

C#
//----------------deferred query----------------------
Console.WriteLine("-----------------DEFERRED QUERY--------------------");
int[] arr = new int[] { 1, 2, 3, 4, 5 };
IEnumerable<int> ints = arr.Select(i => i);
foreach (int i in ints)
{
    Console.WriteLine(i);//result is 1,2,3,4,5
}
arr[0] = 10;
foreach (int i in ints)
{
    Console.WriteLine(i);//result is 10,2,3,4,5
}

 

Func Delegates

Some of the Standard Query Operators take Func delegate as an argument. Below are Func delegate declarations:

C#
public delegate TR Func<TR>();
public delegate TR Func<T0, TR>(T0 a0);
public delegate TR Func<T0, T1, TR>(T0 a0, T1 a1);
public delegate TR Func<T0, T1, T2, TR>(T0 a0, T1 a1, T2 a2);
public delegate TR Func<T0, T1, T2, T3, TR>(T0 a0, T1 a1, T2 a2, T3 a3);

In these declarations TR is a type of retuned value.  Other T0, T1, etc. are parameters for function. When you call any of the operators that require some Func delegate as input parameter it is usually called as predicate. In this predicate you will usually have some input types and latest argument should be returned type.

For example let’s review Count operator from IEnumerable it has following definition from MSDN:

Count<TSource>(IEnumerable<TSource>, Func<TSource, Boolean>)

Returns a number that represents how many elements in the specified sequence satisfy a condition.

Here is C# declaration:

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

As you can see here second argument is delegate that receives source as an input and return bool value that indicate if input source value satisfy some condition. You should be familiar with Func Delegates syntax to understand what is required from you.

LINQ to Objects deferred operators

Below I'm going to review deferred operators of LINQ to Objects that are present for the moment. Almost all samples will be executing using following data structure:

C#
string[] CarBrands = { "Mersedes", "Ford", "Lexus", "Toyota",
                       "Honda", "Hyunday", "BMW", "KIA", "Chevrolet",
                       "Tesla", "Lamborghini", "Ferrari", "Lincoln",
                        "Cadillac"};

For more details, please download source code attached to this article and execute it to see results.

 

  • Where - this is an operator that is used to filter elements of input data structure into output sequence. Where has two overloads:
    • public static IEnumerable<T> Where<T>(this IEnumerable<T> source,Func<T, bool> predicate); - this version takes input sources and predicate method  delegate and returns sequence of object that got true from predicate method. As you can see predicate method should take type T and return bool. Where is taking each element of input sequence and pass it to predicate method.
    • public static IEnumerable<T> Where<T>(this IEnumerable<T> source,Func<T, int, bool> predicate); - this version is similar to the first one but in addition it receives one input integer value to predicate function. This integer value is an index number for the element from input sequence. You should remember that index is zero based, so the first element index will be 0 and the last one equal to number of elements in sequence minus one.

CODE:

C#
Console.WriteLine("-----------------WHERE");
//first version
Console.WriteLine("--first version");
IEnumerable<string> outCars = CarBrands.Where(s => s.StartsWith("C"));//gives Chevrole and Cadillac
foreach (string car in outCars)
{
    Console.WriteLine(car);
}
Console.WriteLine("--second version");
outCars = CarBrands.Where((s, i) => { return (i & 1) == 0; });//gives cars whose index is odd
foreach (string car in outCars)
{
    Console.WriteLine(car);
}

RESULT:

Image 1

  • Select - is an operator that is used to create output sequence of one type of element from input sequence of another type of element. Input sequence and output  sequence elements can be of the same type as well, but also may not be. This function has two overloads:
    •  public static IEnumerable<S> Select<T, S>(this IEnumerable<T> source,Func<T, S> selector); - this version takes input sequence and selector method delegate.
    • public static IEnumerable<S> Select<T, S>(this IEnumerable<T> source,Func<T, int, S> selector); - second version of select that besides next element receives input index number for that element. Same as inde in Where operator.

CODE:

C#
Console.WriteLine("--first version");
//here we transofrm input sequence of strings to output sequence of integers that present their lenghts
IEnumerable<int> lengths = CarBrands.Select( s => s.Length);
foreach (int i in lengths)
{
    Console.WriteLine(i);
}
Console.WriteLine("--second version");
var CarsAndTheirIndexes = CarBrands.Select((s, i) => new { CarName = s, CarIndex = i});
foreach (var Car in CarsAndTheirIndexes)
{
    Console.WriteLine("Car name is: " + Car.CarName + " Car index is: " + Car.CarIndex);
}

RESULT:

Image 2

 

  • SelectMany - is an operator that is used to create one-to-many projections ….There are two prototypes of this function as well:
    • public static IEnumerable<S> SelectMany<T, S>(this IEnumerable<T> source,Func<T, IEnumerable<S>> selector); - this version takes input sequence as source and selector delegate. This delegate receives as an input each element of input sequence. It should return object that when you enumerate it will yield zero or more elements of type S to output sequence. The result of SelectMany operator will be concatenated output from all these sequences of type S.
    • public static IEnumerable<S> SelectMany<T, S>(this IEnumerable<T> source,Func<T, int, IEnumerable<S>> selector); - second version is exact the same as first one but with index of the element added as input to selector method.

CODE:

C#
Console.WriteLine("--first version");
//here we return list of characters for a car that starts with C and othervise return empty array
var CarsLetters = CarBrands.SelectMany(car => { if(car.StartsWith("C"))return car.ToArray(); return new char[0];});
foreach (char c in CarsLetters)
{
    Console.Write(c);
}
Console.WriteLine("--second version");
CarsLetters = CarBrands.SelectMany((car,index) => { if((index&1) == 0)return car.ToArray(); return new char[0]; });
foreach (char c in CarsLetters)
{
    Console.Write(c);
}

RESULT:

Image 3

  • Take - returns specific number of elements in sequence starting from beginning of sequence. There is only one prototype for this operator:
    • public static IEnumerable<T> Take<T>(this IEnumerable<T> source,int count); - function returns sequence of count basing on input integer that  indicates how many elements from input sequence are present in output one. Note if you put number of requested elements bigger than number of elements in input sequence you'll not have any error and your output sequence will have all elements from input one.

CODE:

C#
Console.WriteLine("\n-----------------TAKE");
IEnumerable<string> ThreeCars = CarBrands.Take(3);
foreach (string car in ThreeCars)
{
    Console.WriteLine(car);
}

RESULT:

Image 4

  • TakeWhile - this operator takes elements from input sequence and put them to output one while some condition is true. There are two prototypes for this operator:
    • public static IEnumerable<T> TakeWhile<T>(this IEnumerable<T> source,Func<T, bool> predicate); - this function takes input sequence and predicate function that will receive each element from input sequence and should return bool. Once false is returned operator stops to yield elements from input sequence and ends its work.
    • public static IEnumerable<T> TakeWhile<T>(this IEnumerable<T> source,Func<T, int, bool> predicate); - same as first version but with input index of element in input sequence

CODE:

C#
Console.WriteLine("\n-----------------TAKEWHILE");
Console.WriteLine("--first version");
cars = CarBrands.TakeWhile(s => s.Length > 3);//will return all cars until we reach BMW
foreach (string car in cars)
{
    Console.WriteLine(car);
}
Console.WriteLine("--second version");
cars = CarBrands.TakeWhile((s,i) => i < 5);//will return first 5 cars
foreach (string car in cars)
{
    Console.WriteLine(car);
}

RESULT:

Image 5

  • Skip - this operator is opposite to Take one. It skips specific numbers of elements in input sequence and yields all the rest till the end. It has one prototype:
    • public static IEnumerable<T> Skip<T>(this IEnumerable<T> source,int count); - this function return sequence basing on input one but without number of elements from the beginning that is defined by count value
CODE:

            Console.WriteLine("\n-----------------SKIP");
            IEnumerable<string> CarsWithoutFirstThree = CarBrands.Skip(3);
            foreach (string car in CarsWithoutFirstThree)
            {
                Console.WriteLine(car);
            }

RESULT:

Image 6

  • SkipWhile - this operator will skip elements of input sequence while some condition is true. Once this condition is false it will return all the rest elements to output sequence. Has two versions:
    • public static IEnumerable<T> SkipWhile<T>(this IEnumerable<T> source,Func<T, bool> predicate); - this function takes input sequence and pass all its elements to predicate function. These elements will be skipped while predicate return true. Once it returned false for specific element you will receive all the rest elements in input sequence to output one.
    • public static IEnumerable<T> SkipWhile<T>(this IEnumerable<T> source,Func<T, int, bool> predicate); - same as previous one but predicate receives also index of input element

CODE:

C#
Console.WriteLine("\n-----------------SKIPWHILE");
Console.WriteLine("--first version");
cars = CarBrands.SkipWhile(s => !s.StartsWith("C"));//will return all cars after Chevrole including it also
foreach (string car in cars)
{
    Console.WriteLine(car);
}
Console.WriteLine("--second version");
cars = CarBrands.SkipWhile((s, i) => i < 5);//will return cars without first 4
foreach (string car in cars)
{
    Console.WriteLine(car);
}

RESULT:

Image 7

  • Concat - this operator concatenates two input sequences. It has following prototype:
    • public static IEnumerable<T> Concat<T>(this IEnumerable<T> first,IEnumerable<T> second); - here two sentences of the same type are input, result will be combination of them

CODE:

C#
Console.WriteLine("\n-----------------CONCAT");
string[] FewMoreCars = { "Acura", "Infinity" };
cars = CarBrands.Concat(FewMoreCars);
foreach (string car in cars)
{
    Console.WriteLine(car);
}

RESULT:

Image 8

  • OrderBy - using this operator you may order input sequence based on a method keySelector that will return key value for each element. This operator returns as a result ordered sequence IOrderedEnumerable<T> that will be yielded basing on values of keys. This operator has following prototypes:
    • public static IOrderedEnumerable<T> OrderBy<T, K>(this IEnumerable<T> source,Func<T, K> keySelector) where K : IComparable<K>; - this function takes input sequence source and delegate function keySelector that takes every element of the source and return a key based on it. Than this method orders all the elements in input sequence basing on this key. Type of returnable key from keySelector must implement IComparable interface.
    • public static IOrderedEnumerable<T> OrderBy<T, K>(this IEnumerable<T> source,Func<T, K> keySelector,IComparer<K> comparer); - similar to first version but here you pass IComparer object to this function and it will do comparison for two K instances. In this case it is not necessary that value returned by keySelector is implemeting IComparable.

CODE:

C#
            Console.WriteLine("\n-----------------ORDERBY");
            Console.WriteLine("--first version");
            cars = CarBrands.OrderBy(s => s.Length);
            foreach (string car in cars)
            {
                Console.WriteLine(car);
            }
            Console.WriteLine("--second version");
            MyStringComparer comparer = new MyStringComparer();
            cars = CarBrands.OrderBy((s => s), comparer);
            foreach (string car in cars)
            {
                Console.WriteLine(car);
            }

RESULT:

Image 9

  • OrderByDescending - same operator as OrderBy but orders in descending order. Also has two prototypes:
    • public static IOrderedEnumerable<T> OrderByDescending<T, K>(this IEnumerable<T> source,Func<T, K> keySelector) where K : IComparable<K>; - this function takes input sequence source and delegate function keySelector that takes every element of the source and return a key based on it. Than this method orders all the elements in input sequence basing on this key but descenging. Type of returnable key from keySelector must implement IComparable interface.
    • public static IOrderedEnumerable<T> OrderByDescending<T, K>(this IEnumerable<T> source,Func<T, K> keySelector,IComparer<K> comparer); - similar to first version but here you pass IComparer object to this function and it will do comparison for two K instances. In this case it is not necessary that value returned by keySelector is implemeting IComparable.

CODE:

C#
Console.WriteLine("\n-----------------ORDERBYDESCENDING");
Console.WriteLine("--first version");
cars = CarBrands.OrderByDescending(s => s.Length);
foreach (string car in cars)
{
    Console.WriteLine(car);
}
Console.WriteLine("--second version");
cars = CarBrands.OrderByDescending((s => s), comparer);
foreach (string car in cars)
{
    Console.WriteLine(car);
}

RESULT:

Image 10

  • ThenBy - this operator takes already ordered sequence as IOrderedEnumearble and order it again basing on method that will return key value. Has two prototypes:
    • public static IOrderedEnumerable<T> ThenBy<T, K>(this IOrderedEnumerable<T> source,Func<T, K> keySelector)where K : IComparable<K>; - this function takes as an input IOrderedEnumearable sequence and return same data structure as output. keySelector delegate takes each element of input sequence and returns key based on input element. Returned key must implement IComparable.
    • public static IOrderedEnumerable<T> ThenBy<T, K>(this IOrderedEnumerable<T> source,Func<T, K> keySelector,IComparer<K> comparer); - same as first version, but returned key implements IComparer.

CODE:

C#
Console.WriteLine("\n-----------------THENBY");
Console.WriteLine("--first version");
cars = CarBrands.OrderBy(s => s.Length).ThenBy(s => s);
foreach (string car in cars)
{
    Console.WriteLine(car);
}
Console.WriteLine("--second version");
comparer = new MyStringComparer();
cars = CarBrands.OrderBy(s => s.Length).ThenBy(s=>s, comparer);
foreach (string car in cars)
{
    Console.WriteLine(car);
}

RESULT:

Image 11

Important note about ThenBy operator that it guarantees that if two elements that method receives one by one of input sequence has same key value, they will be in exact same order in output sequence.

 

  • ThenByDescending - exactly as previous operator, but sorts in descending order:
    • public static IOrderedEnumerable<T> ThenByDescending<T, K>(this IOrderedEnumerable<T> source,Func<T, K> keySelector)where K : IComparable<K>; - this function takes as an input IOrderedEnumearable sequence and return same data structure as output. keySelector delegate takes each element of input sequence and returns key based on input element. Returned key must implement IComparable.
    • public static IOrderedEnumerable<T> ThenByDescending<T, K>(this IOrderedEnumerable<T> source,Func<T, K> keySelector,IComparer<K> comparer); - same as first version, but returned key implements IComparer.

CODE:

C#
Console.WriteLine("\n-----------------THENBYDESCENDING");
Console.WriteLine("--first version");
cars = CarBrands.OrderBy(s => s.Length).ThenByDescending(s => s);
foreach (string car in cars)
{
    Console.WriteLine(car);
}
Console.WriteLine("--second version");
comparer = new MyStringComparer();
cars = CarBrands.OrderBy(s => s.Length).ThenByDescending(s => s, comparer);
foreach (string car in cars)
{
    Console.WriteLine(car);
}

RESULT:

Image 12

Same as ThenBy, ThenByDescending operator that it guarantees that if two elements that method receives one by one of input sequence has same key value, they will be in exact same order in output sequence.

 

  • Reverse - returns same sequence as it receives for input, but in reverse order. Has one prototype:
    • public static IEnumerable<T> Reverse<T>(this IEnumerable<T> source); - nothing complicated one input parameter source that presents input sequence.

CODE:

C#
Console.WriteLine("\n-----------------REVERSE");
cars = CarBrands.Reverse();
foreach (string car in cars)
{
    Console.WriteLine(car);
}

RESULT:

Image 13

  • Join - this operator returns combination of elements from two sequences where keys extracted from each sequence elements match. Has following prototype:
    • public static IEnumerable<V> Join<T, U, K, V>(this IEnumerable<T> outer,IEnumerable<U> inner,Func<T, K> outerKeySelector,Func<U, K> innerKeySelector, Func<T, U, V> resultSelector); - here first argument outer is a sequence on which we call Join operator as this function is extension method. Second argument called inner presents another sequence that we pass as input for this method. First innerKeySelector is called for each element of inner sequence it will return key for each element and each element of inner sequence will be stored in hash table basing on its key. Next outer sequence is enumerated and outerKeySelector is called for each element of it to get its key. If we have matched by key element in hash table of inner elements then resultSelctor will be called. resultSelector method will return instantiated object of type V.

CODE:

C#
Console.WriteLine("\n-----------------JOIN");
int[] Lenghts = { 3, 4, 5, 6, 7, 8, 9 };
var outStructs = CarBrands.Join(Lenghts, s => s.Length, l => l, (s, l) => new { name = s, length = l });
foreach (var Car in outStructs)
{
    Console.WriteLine("Car name is: " + Car.name + " Car name length is: " + Car.length);
}

RESULT:

Image 14

  • GroupJoin - performs grouped join on two input sequence. Similar to Join operator, but it groups all matching inner elements to one element in outer sequence. Function has following prototype:
    • public static IEnumerable<V> GroupJoin<T, U, K, V>(this IEnumerable<T> outer,IEnumerable<U> inner,Func<T, K> outerKeySelector,Func<U, K> innerKeySelector, Func<T, IEnumerable<U>, V> resultSelector); - here outer parameter is the sequence on which we call the function, inner parameter is the input sequence. Firs this operator will call innerKeySelector method to enumerate all elements of type U of input sequence and store all elements of this sequence in hash table referenced by key K. Next step will be calling outerKeySelector for each element of type T and to retrieve its key K. When they key K is retrieved it obtains all elements of type inner sequence that are stored in hash table whose key and match key K. Final step will be calling result selector method and passing to it outer element and sequence of inner elements whose keys match outer element keys.

CODE:

C#
Console.WriteLine("\n-----------------GROUPJOIN");
//if car length is bigger than 5 we take all lengths that bigger than 5
//if car length is smaller or equal 5 we take all lengths that smaller or equal 5
int[] Len = { 3, 4, 5, 6, 7, 8, 9 };
var outValues = CarBrands.GroupJoin(Len, s => s.Length > 5, l => l > 5, (s, lens) => new { name = s, lensplus = lens });
foreach (var car in outValues)
{
    Console.WriteLine("Car name is: " + car.name + "name length: "+ car.name.Length +" Lengths bigger or smaller and equal than 5: ");
    foreach (int l in car.lensplus)
    {
        Console.WriteLine(l);
    }
}

RESULT:

Image 15

  • GroupBy - this operator groups elements of input sequence by common key. Has following prototypes:
    • public static IEnumerable<IGrouping<K, T>> GroupBy<T, K>(this IEnumerable<T> source,Func<T, K> keySelector); - this function enumerates input source sequence and calls keySelector method for each element. Then with this element and its key this function yields the sequence IGrouping<K,T> where each element of this group is the sequence of elements with the same key. Key values are compared using default equality comparer.
    • public static IEnumerable<IGrouping<K, T>> GroupBy<T, K>(this IEnumerable<T> source,Func<T, K> keySelector,IEqualityComparer<K> comparer);  - this prototype is exact the same like first one, but you also put your own equality comparer instead of default one.
    • public static IEnumerable<IGrouping<K, E>> GroupBy<T, K, E>(this IEnumerable<T> source,Func<T, K> keySelector,Func<T, E> elementSelector); - same as first prototype but instead of all sequence you can specify which elements to walk thru using elementSelector.
    • public static IEnumerable<IGrouping<K, E>> GroupBy<T, K, E>(this IEnumerable<T> source,Func<T, K> keySelector,Func<T, E> elementSelector,IEqualityComparer<K> comparer); - fourth prototype is the combination of second and third here you may decide which elements to group and provide your own comparer for key.

CODE:

C#
Console.WriteLine("\n-----------------GROUPBY");
Console.WriteLine("--first version");
IEnumerable<IGrouping<int, string>> grouppedCars = CarBrands.GroupBy(s => s.Length);
foreach(IGrouping<int, string> element in grouppedCars)
{
    Console.WriteLine("Length: " + element.Key + " Names:");
    foreach(string s in element)
    {
        Console.WriteLine(s);
    }
}
Console.WriteLine("--second version");
IntComparer comp = new IntComparer();
grouppedCars = CarBrands.GroupBy(s => s.Length,comp);
foreach (IGrouping<int, string> element in grouppedCars)
{
    Console.WriteLine("Length: " + element.Key + " Names:");
    foreach (string s in element)
    {
        Console.WriteLine(s);
    }
}
Console.WriteLine("--third version");
IEnumerable<IGrouping<int, int>> grouppedLengths = CarBrands.GroupBy(s => s.Length, el => el.Length);
foreach (IGrouping<int, int> element in grouppedLengths)
{
    Console.WriteLine("Length group: " + element.Key + " Cars lengths:");
    foreach (int s in element)
    {
        Console.WriteLine(s);
    }
}
Console.WriteLine("--fourth version");
grouppedLengths = CarBrands.GroupBy(s => s.Length, el => el.Length, comp);
foreach (IGrouping<int, int> element in grouppedLengths)
{
    Console.WriteLine("Length group: " + element.Key + " Cars lengths:");
    foreach (int s in element)
    {
        Console.WriteLine(s);
    }
}

RESULT:

Image 16

Image 17

  • Distinct - this operator removes all duplicates from input source and returns new sequence without any duplicates. Has following prototype:
    • public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source); - as you can see this is very simple function that we call at source sequence and that returns new sequence of the same type

CODE:

C#
Console.WriteLine("\n-----------------DISTINCT");
string[] names = { "Sergey", "Sergey", "John", "James", "John", "Mary", "Alexander" };
IEnumerable<string> dist = names.Distinct();
foreach (string s in dist)
{
    Console.WriteLine(s);
}

RESULT:

Image 18

  • Union - this operator returns set of unique values of two input sequences has following prototype:
    • public static IEnumerable<T> Union<T>(this IEnumerable<T> first,IEnumerable<T> second); - this functions first enumerates input sequence named first and takes elements that were not taken yet, then it enumerates sequence called second exact same way.

CODE:

C#
Console.WriteLine("\n-----------------UNION");
string[] names2 = { "Mary", "Alexander", "James", "Richard", "Julia" };
IEnumerable<string> uniqueNames = names.Union(names2);
foreach (string s in uniqueNames)
{
    Console.WriteLine(s);
}

RESULT:

Image 19

  • Intersect - returns intersection of two input sequences. Has following prototype:
    • public static IEnumerable<T> Intersect<T>(this IEnumerable<T> first,IEnumerable<T> second); - this function first enumerates elements of input sequence named second taking only unique elements and then enumerates elements from first sequence taking those that were enumerated at second and put them to output sequence.

CODE:

C#
Console.WriteLine("\n-----------------INTERSECT");
IEnumerable<string> intersNames = names.Intersect(names2);
foreach (string s in intersNames)
{
    Console.WriteLine(s);
}

RESULT:

Image 20

  • Except - this operator returns all elements from first sequence that don't exist in second. Following function prototype exists for this operator:
    • public static IEnumerable<T> Except<T>(this IEnumerable<T> first,IEnumerable<T> second); - pretty simple function. We call it on first sequence and put second as input.

CODE:

C#
Console.WriteLine("\n-----------------EXCEPT");
IEnumerable<string> exceptNames = names.Except(names2);
foreach (string s in exceptNames)
{
    Console.WriteLine(s);
}

RESULT:

Image 21

  • Cast - this operator cases each element of input sequence to output sequence of specific type. Has one prototype:
    • public static IEnumerable<T> Cast<T>(this IEnumerable source); - as you can see this operator is called on a sequence source of type IEnumerable. As you can see form all previous operators most of them take IEnumearble<T> and not IEnumearble, so please don't be confused here it is indeed different. NOTE: cast will try to convert every object of input sequence to output sequence type. If something is not convertible you'll get InvalidCastException.

CODE:

C#
Console.WriteLine("\n-----------------CAST");
int[] integers = { 1, 2, 3, 4, 5, 6, 7 };
IEnumerable<object> objs = integers.Cast<object>();
foreach (object s in objs)
{
    Console.WriteLine(s.ToString());
}

RESULT:

Image 22

  • OfType - this operator is similar to Cast, but it returns only those elements that can be cast to output sequence type. It has following prototype:
    • public static IEnumerable<T> OfType<T>(this IEnumerable source); - same as Сast it takes IEnumerable interface.

CODE:

C#
Console.WriteLine("\n-----------------OFTYPE");
IEnumerable<string> strs = integers.OfType<string>();//here we'll receive empty string
foreach (string s in strs)
{
    Console.WriteLine(s.ToString());
}

RESULT:

Image 23

  • DefaultIfEmpty - this function returns sequence containing default value if input sequence is empty. Has following prototypes:
    • public static IEnumerable<T> DefaultIfEmpty<T>(this IEnumerable<T> source); - this function is called on input sequence source. If input sequence is empty it returns one element of input sequence default<T> which is null for reference and nulable types.
    • public static IEnumerable<T> DefaultIfEmpty<T>(this IEnumerable<T> source,T defaultValue); - same as first, but gives ability to set default value.

CODE:

C#
Console.WriteLine("\n-----------------DEFAULTIFEMPTY");
Console.WriteLine("--first version");
IEnumerable<int> sequence = integers.Where(i => i>10);
IEnumerable<int> outseq = sequence.DefaultIfEmpty();//here we should have default int value
Console.WriteLine("Result on empty:");
foreach(int i in outseq)
{
    Console.WriteLine(i);
}
sequence = integers.Where(i => i > 5);
outseq = sequence.DefaultIfEmpty();//here we should have default int value
Console.WriteLine("Result on not empty:");
foreach (int i in outseq)
{
    Console.WriteLine(i);
}
Console.WriteLine("--second version");
sequence = integers.Where(i => i > 10);
outseq = sequence.DefaultIfEmpty(55);//here we should have default int value
Console.WriteLine("Result on empty:");
foreach (int i in outseq)
{
    Console.WriteLine(i);
}

RESULT:

Image 24

  • Range - generates sequence of integers has following prototype:
    • public static IEnumerable<int> Range(int start,int count);

CODE:

C#
Console.WriteLine("\n-----------------RANGE");
IEnumerable<int> intseq = Enumerable.Range(5, 5);
foreach (int i in intseq)
{
    Console.WriteLine(i);
}

RESULT:

Image 25

  • Repeat - this operator generates output sequence that contains specific input element for specific number of times. Has one prototype
    • public static IEnumerable<T> Repeat<T>(T element,int count);

CODE:

C#
Console.WriteLine("\n-----------------REPEAT");
intseq = Enumerable.Repeat(5, 5);
foreach (int i in intseq)
{
    Console.WriteLine(i);
}

RESULT:

Image 26

  • Empty - generates empty sequence of specific type. Has one prototype:
    • public static IEnumerable<T> Empty<T>();

CODE:

C#
Console.WriteLine("\n-----------------EMPTY");
intseq = Enumerable.Empty<int>();
foreach (int i in intseq)
{
    Console.WriteLine(i);//we should never reach here
}

RESULT:

Image 27

Sources

  1. Pro LINQ Language Integrated Query in C# 2010 Adam Freeman and Joseph C. Rattz, Jr.
  2. https://msdn.microsoft.com
  3. http://www.codeproject.com/Articles

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)