LINQ, which is short for Language Integrated Query, was introduced to the .NET framework with version 3.5 back in 2007. The core concept was to extend the framework—and, along with it, the C# language—with query expression capabilities. We differentiate these capabilities into two groups: query syntax and method syntax. The first is more like a SQL query to filter out different data which fit a certain criteria, while the second is done with method calls similar to the keywords of SQL queries. It allows you to work with different types of data, such as SQL databases, XML documents, and responses from web services.
In this guide, we will see examples of both these syntaxes, and we will discuss deferred execution and lazy and eager evaluation.
This is a small demonstration of query syntax. Let's say we have a list of servers with their functionality.
1IList<string> serverList = new List<string>() {
2 "Parabellum - Domain Controller",
3 "Prostetic - DNS",
4 "Spooky - Domain Controller",
5 "Scar - DHCP" ,
6 "BigBoy - Web Server"
7};
Let's say we would like to query all the Domain Controllers in the list.
With the query syntax method, we can achieve this the following way.
1var DCS = from s in serverList
2 where s.Contains("Domain Controller")
3 select s;
With method syntax, this is the way to go about it.
1var DCS = serverList.Where(s => s.Contains("Domain Controller"));
In both cases, the DCS
variable holds a list with two matching servers.
In order to dig deeper into LINQ and its other benefits, we need to understand deferred execution. In most cases, during compilation, LINQ expressions are evaluated on a specific dataset and the variables are initialized with filtered values. By implementing deferred execution, we can save valuable CPU and RAM resources because we don't have to evaluate a specific expression or delay the evaluation until the realized
value is actually required. There are two cases when this is very useful. The first is when your application is working on multiple gigabytes or even terrabytes of data, and the second is when you have chained together multiple queries that result in different manipulations of the dataset.
In order to deepen our understanding of deferred execution, we need to introduce the keyword yield
. This keyword was introduced with version 2.0 of C#. It is vital for lazy evaluation and improves the performance of LINQ queries. The keyword yield
is contextual, which means it can be used as a variable name without any problems. Let's create a small app that uses yield
to iterate over the servers.
1using System;
2using System.Collections.Generic;
3
4namespace Pluralsight
5{
6 public class Server {
7 string _name;
8 string _function;
9 public string Name {
10 get { return _name; }
11 set { _name = value; }
12 }
13 public string Function {
14 get { return _function; }
15 set { _function = value; }
16 }
17 public Server(string Name, string Function) {
18 _name = Name;
19 _function = Function;
20 }
21 }
22 public class Yielder
23 {
24 public static IEnumerable<Server> ServerDB()
25 {
26 Server[] servers = new Server[]
27 {
28 new Server("Parabellum","Domain Controller"),
29 new Server("Pluck","DNS Server"),
30 new Server("Pilgrim","Web Server"),
31 new Server("Spark","Firewall Server"),
32 };
33 foreach(Server s in servers)
34 {
35 yield return s;
36 }
37 }
38 public static void Main()
39 {
40
41 foreach(Server s in ServerDB())
42 {
43 Console.WriteLine($"Name: {s.Name}, Function: {s.Function}");
44 }
45
46 Console.ReadKey();
47 }
48 }
49}
This produces the following output.
1Name: Parabellum, Function: Domain Controller
2Name: Pluck, Function: DNS Server
3Name: Pilgrim, Function: Web Server
4Name: Spark, Function: Firewall Server
The key here is the yield
, which allows us to iterate over each element of our Server[]
list. It does not return the whole list at once, thus saving memory and improving performance. This is also an example of the deferred execution, as the foreach
loop pulled out items one by one, iterating over them. If we want, we can play around with it by placing the following line after the yield return s;
section.
1Console.WriteLine($"Sending {s.Name}");
This would modify the output in the following way.
1Name: Parabellum, Function: Domain Controller
2Sending Parabellum
3Name: Pluck, Function: DNS Server
4Sending Pluck
5Name: Pilgrim, Function: Web Server
6Sending Pilgrim
7Name: Spark, Function: Firewall Server
8Sending Spark
Note how Sending...
appears after each element in the array.
The use of lazy evaluation means that only a single element of the source collection is processed during each call to the iterator. An iterator can be a custom class
or a foreach
or while
loop, depending on your context.
The opposite of this is usually eager evaluation. This means the first call to the iterator will return the entire collection, or dataset.
In this guide, we have looked at how LINQ revolutionized the way programmers work on different datasets and interact with different datasources. We have seen some examples of query and method syntax, and we learned the difference between lazy and eager evaluation. I hope this guide has been informative to you and I would like to thank you for reading it!