I will tell you about the basics of embedded LINQ language. It will make it easier for you to work with the C# language. Instead of having to deal with an entirely new set of tools in the form of classes, you can use all the same familiar collections and arrays with existing classes. This means that you can take full advantage of LINQ queries with minimal or no modifications to existing code. LINQ to Objects functionality is provided by the IEnumerable interface, sequences, and standard query operations.
Introduction
Let's take a look at what LINQ is. LINQ is Language-Integrated Query. The data source can be an object (implements the IEnuberable
interface, which are standard collections, arrays), an XML document, and a DBSet
data set. However, regardless of the data source, LINQ implements the same approach for fetching from that data. In addition, there are many varieties of LINQ:
- LINQ to Objects: Used to work with arrays and collections
- LINQ to Entities: Used when accessing databases through Entity Framework technology
- LINQ to SQL: Data Access Technology in MS SQL Server
- LINQ to XML: Used when working with XML files
- LINQ to DataSet: Used when working with a DataSet object
- Parallel LINQ (PLINQ): Used to execute parallel queries
In this article, I want to talk first of all about the first LINQ language varieties.
Background
First of all, for understanding what it is, I would show you the first instance:
string[] Contries = { "USA", "Canada", "United Kingdom", "Mexico", "China", "Uruguay" };
var SelectedContr = new List<string>();
foreach(string c in Contries)
{
if (c.StartsWith("U"))
SelectedContr.Add(c);
}
foreach (string s in SelectedContr)
Console.WriteLine(s);
In it, we create some data array, and then we loop through it and check the match at each iteration, if this is true
, then we write to the collection. What if I say that this code can be shortened several times and the loop in which the condition is checked can be removed? Consider the code written using the LINQ language. To use the LINQ functionality, make sure the System.LINQ
namespace is included in the file.
string[] Contries = { "USA", "Canada", "United Kingdom", "Mexico", "China", "Uruguay" };
var SelectedContr = from C in Contries
where c.StartsWith("U")
select C;
foreach (string s in SelectedContr)
Console.WriteLine(s);
We have the same result, but it is achieved in an easier way. The definition in the language is:
from variable in object_set
select variable;
So what did we do in our example, let's go step by step. The expression from C in Countries defines C as each element of the array and as a result, we can perform various operations with it. MS Visual Studio automatically recognizes that the set c consists of string objects, so the C variable will be treated as a string
. This does not mean that LINQ is not strongly typed. Using where, objects are filtered by a certain criterion, but in the case found it starts with the letter “U
”. We use the select
statement to pass the selected values into the result set that is returned by the LINQ expression.
The advantage of such queries is that they are intuitively similar to SQL queries, although they have some differences. In addition, we have a huge list of extension methods besides from… in… select
. Here is the entire list:
Select
: defines the projection of the selected values Where
: defines a selection filter OrderBy
: orders items in ascending order OrderByDescending
: orders items in descending order ThenBy
: sets additional criteria for ordering items in ascending order ThenByDescending
: specifies additional criteria for ordering items in descending order Join
: joins two collections on a specific basis GroupBy
: groups items by key ToLookup
: groups items by key, with all items added to the dictionary GroupJoin
: performs both collection joining and grouping of items by key Reverse
: reverse the order All
: determines if all items in the collection meet a specific condition Any
: determines if at least one element of the collection meets a certain condition Contains
: determines if the collection contains a specific element Distinct
: removes duplicate items from a collection Except
: returns the difference of two collections, that is, those items that are created in only one collection Union
: combines two homogeneous collections Intersect
: returns the intersection of two collections, that is, those items that occur in both collections Count
: counts the number of elements in the collection that meet a specific condition Sum
: calculates the sum of numeric values in a collection Average
: calculates the average of the numeric values in the collection Min
: finds the minimum value Max
: finds the maximum value Take
: selects a certain number of items Skip
: skips a certain number of items TakeWhile
: returns a chain of sequence elements as long as the condition is true
SkipWhile
: skips elements in a sequence as long as they satisfy a given condition, and then returns the remaining elements Concat
: combines two collections Zip
: combines two collections according to a specific condition First
: selects the first item in the collection FirstOrDefault
: selects the first item in the collection or returns the default Single
: selects a single element of the collection, if the collection contains more or less than one element, an exception is thrown SingleOrDefault
: selects the first item in the collection or returns the default ElementAt
: selects an element of a sequence at a specific index ElementAtOrDefault
: selects a collection element at a specific index or returns a default value if the index is out of range Last
: selects the last item in the collection LastOrDefault
: selects the last item in the collection or returns the default
Let's take a closer look at some of the operations in LINQ.
Sorting
The linq language for sorting a dataset in ascending order uses the oderby
operator. Let's look at the simplest example for sorting an array.
int[] num = { 5, 7, 9, 3, 19, 25, 34 };
var numord = from j in num
orderby j
select j;
foreach (int c in numord)
Console.WriteLine(c);
By default, the orderby
operator sorts in ascending order. However, using the keywords ascending
(sorting in ascending order) and descending
(sorting in descending order), you can explicitly specify the direction of sorting: As in the example, we sorted the array by numbers, but the language allows you to sort more complex objects. Consider an example:
class People
{
public string Name { get; set; }
public int Age { get; set; }
}
List<People> people = new List<People>
{
new People {Name = "Aron",Age= 19},
new People {Name = "Bill",Age = 33},
new People {Name = "Tom", Age = 7}
};
var sortedpeople = people.OrderBy(n => n.Name);
As you can see from the example, we have used the OrderBy
extension method instead of the orderby
operator. Moreover, you can use multiple sorting criteria. Consider the following example:
var result = from person in people
orderby person.Name, person.Age
select person;
Here, we used two sorting criteria - name
and age
.
Filtering a Selection
Where
method is used to select elements from a set by condition. Let's consider an example:
class People
{
public string Name { get; set; }
public int Age { get; set; }
}
List<People> People = new List<People>
{
new People {Name = "Aron",Age= 5},
new People {Name = "Pamela",Age = 5},
new People {Name = "George",Age = 10},
new People {Name = "Mark",Age = 30},
new People {Name = "David",Age = 25},
new People {Name = "Tim",Age = 25},
new People {Name = "Jerry",Age = 25},
new People {Name = "Tom", Age = 10}
};
IEnumerable<People> peoples = from p in People
where p.Age == 25
select p;
foreach (var i in peoples)
Console.WriteLine(i.Name);
Here, we have sorted people with an age of 25
. The same request using an extension method:
IEnumerable<People> peoples = People.Where(i => i.Age == 25) ;
Union, Intersection and Difference of Collections
In addition to sampling methods, LINQ has several methods that allow you to generate a set of unique elements from two sequences of objects: difference, union, and intersection. Using the Except
method, you can get the difference of two sets.
string[] Female = { "Marta", "Dora", "Jane" , "Tim", "Tom", "Ben" };
string[] Man = { "Tim", "Tom", "Ben" };
var result = Female.Except(Man);
foreach (string n in result)
Console.WriteLine(n);
Here, all elements that are in the Man
array are removed from the Female
array.
To obtain the intersection of sequences, that is, common to both sets of elements, the Intersect
method is used. Consider an example:
string[] Female = { "Marta", "Dora", "Jane", "Tim" };
string[] Man = { "Tim", "Tom", "Ben" };
var result = Female.Intersect(Man);
foreach (string n in result)
Console.WriteLine(n);
In this case, we will have the result "Tim
" since only this element is common in these two arrays.
The Union
method is used to combine two sequences. Let's consider an example:
string[] Female = { "Marta", "Dora", "Jane", "Tim" };
string[] Man = { "Tim", "Tom", "Ben" };
var result = Female.Union(Man);
foreach (string n in result)
Console.WriteLine(n);
As a result, we will have a new set, which contains elements from both the first and the second array, and without repeating elements. You can use the Distinct
method to remove items.
Skip and Take Methods
LINQ has two methods, Skip()
and Take()
which allow you to create paginated output. Let's consider an example:
int[] num = { 1,2,3,4,5,6,7,8,9 };
var first = num.Take(5);
foreach (int i in first)
Console.WriteLine(i);
var rest = num.Skip(5);
foreach (int i in rest)
Console.WriteLine(i);
As a result, in the first case, we will take the first five elements, and in the second case, we will skip the first five elements. However, there are more interesting methods: TakeWhile()
and SkipWhile()
. Consider an example:
int[] num = {1,1,2,3,4,5 };
foreach (var c in num.TakeWhile(n=>n.Equals(1)))
Console.WriteLine(c);
In our example, the TakeWhile()
method selects a chain of elements starting from the first until they satisfy the condition. The SkipWhile()
method works in a similar way. It loops through the chain of elements, starting at the first element, until they meet a certain condition.
Grouping
To group data by specific parameters, use the groupby
operator or the GroupBy()
method. Let's consider an example:
class People
{
public string Name { get; set; }
public int Age { get; set; }
}
List<People> People= new List<People>
{
new People {Name = "Aron",Age= 5},
new People {Name = "Pamela",Age = 5},
new People {Name = "George",Age = 10},
new People {Name = "Mark",Age = 30},
new People {Name = "David",Age = 25},
new People {Name = "Tim",Age = 25},
new People {Name = "Jerry",Age = 25},
new People {Name = "Tom", Age = 10}
};
var PeopleGroups = from person in People
group person by person.Age;
foreach (IGrouping<int, People> person in PeopleGroups)
{
Console.WriteLine(person.Key);
foreach (var gr in person)
Console.WriteLine(gr.Name);
Console.WriteLine();
}
In this example, we have grouped people by age. There is a peculiarity: If the last operator performing operations on a selection in a LINQ expression is group
, then the select
statement is not applied. The group
operator accepts the criterion by which the grouping is carried out: group person by person.Age
- in this case, grouping by the Age
property.
The group
statement results in a selection that consists of groups. Each group represents an IGrouping <string, People>
object: the string
parameter indicates the type of the key, and the People
parameter indicates the type of the grouped objects.
Each group has a key, which we can get through the Key property: person.Key
All elements of the group can be obtained using additional iteration. Group items are of the same type as the type of objects that were passed to the group
statement, that is, in this case, objects of type People
. The exact same query can be built using the GroupBy
extension method. Consider an example:
var PeopleGroups = People.GroupBy(p => p.Age);
foreach (IGrouping<int, People> person in PeopleGroups)
{
Console.WriteLine(person.Key);
foreach (var gr in person)
Console.WriteLine(gr.Name);
Console.WriteLine();
}
Here, we applied the GroupBy
extension method and ended up with the same result.
Connecting Collections. Join Method
If you need to combine several different types of sets into one, you can use join
operator. The join
operator or the Join()
method is used to join. Typically, this operation is applied to two sets that have one common criterion. Let's consider an example:
List<Country> countries = new List<Country>
{
new Country{Name ="USA",Population =330},
new Country{Name ="Canada",Population = 37}
};
List<Capital> capitals = new List<Capital>
{
new Capital {Name= "Ottawa",CountryName="Canada"},
new Capital {Name="Washington",CountryName="USA"}
};
var result = from c in countries
join cap in capitals on c.Name equals cap.CountryName
select new { Name = c.Name,CapitalName= cap.Name,
Population= c.Population };
foreach (var item in result)
Console.WriteLine($"{item.Name} , {item.CapitalName}, {item.Population}");
The expression join cap in capitals on c.Name equals cap.CountryName
joins the cap
in capitals
list to the c in the countries list if the value of the c.Name
property matches the value of the cap.CountryName
property. The result of the connection will be an object of an anonymous type that will contain three properties.
The same action could be accomplished with the Join()
method:
var result = countries.Join(capitals,
p => p.Name,
t => t.CountryName,
(p, t) => new { Name = p.Name, CapitalName = t.Name,
Population = p.Population });
The Join()
method takes four parameters:
- the second list, which we connect with the current
- property of an object from the current list, through which the connection is made
- property of an object from the second list, along which the connection goes
- a new object that is obtained as a result of the connection
All and Any Methods
If we need to determine whether a collection meets a certain condition, then we can use the Any
, All
, Contains
methods. These methods are boolean and declare true
or false
.
The All
method checks if all items meet the condition. Let's consider an example:
List<People> People= new List<People>
{
new People {Name = "Aron",Age= 5},
new People {Name = "Pamela",Age = 5},
new People {Name = "George",Age = 10},
new People {Name = "Mark",Age = 30},
new People {Name = "David",Age = 25},
new People {Name = "Tim",Age = 25},
new People {Name = "Jerry",Age = 25},
new People {Name = "Tom", Age = 10}
};
bool result = People.All(p => p.Age > 25);
if (result)
Console.WriteLine("All people have age more then 25");
else
Console.WriteLine("We have people with age less 25");
Console.ReadKey();
In this example, we checked the age of the people. Of course, we have an age less than 25
and the result is false
.
The Any
method works in a similar way, only allows you to find out whether at least one element of the collection meets a certain condition, we use the same class and set of objects as in the previous example, but change the condition a little.
bool result = People.Any(u => u.Age > 45);
if (result)
Console.WriteLine("We have somebody more then 45 years");
else
Console.WriteLine("Nobody");
It returns false
, because we don’t have any more then 45 years.
Conclusion
In conclusion, part of what makes LINQ so powerful and easy to use is its tight integration with the C # language. Instead of having to deal with an entirely new set of tools in the form of classes, you can use all the same familiar collections and arrays with existing classes. This means that you can take full advantage of LINQ queries with minimal or no modifications to existing code. LINQ to Objects functionality is provided by the IEnumerable <T>
interface, sequences, and standard query operations.
History
- 11th January, 2021: Initial version