C# LINQ query expression
last modified October 16, 2026
In this article we show how to extract and transform data in C# with LINQ query expressions.
Language-Integrated Query (LINQ) is a set of technologies based on the integration of query capabilities directly into the C# language.
A query is a set of instructions that describes what data to retrieve from a given data source (or sources) and what shape and organization the returned data should have. LINQ query expressions can be used to conveniently extract and process data from arrays, enumerable classes, XML documents, relational databases, and third-party data sources.
Query expressions can be used to query and to transform data from any LINQ-enabled data source. Query expressions have deferred execution. They are not executed until we iterate over the query variable, for example, in a foreach statement.
C# query expression filter
In the next examples, we show how to filter data.
int[] vals = { -2, 4, 6, -1, 2, 0, 1, -3, -4, 2, 3, 8 };
var pos =
from val in vals
where val > 0
select val;
Console.WriteLine(string.Join(" ", pos));
var evens =
from val in vals
where val % 2 == 0
select val;
Console.WriteLine(string.Join(" ", evens));
In the example, we select the positive values and the even values from the array.
var pos =
from val in vals
where val > 0
select val;
With the from clause, we go over the elements of the array. The
elements are filtered with where and finally projected with
select.
$ dotnet run 4 6 2 1 2 3 8 -2 4 6 2 0 -4 2 8
Next we combine conditions with the || operator.
var words = new List<string> { "sky", "bee", "forest", "new", "falcon", "rock",
"cloud", "war", "small", "eagle", "blue", "frost", "water" };
var found =
from word in words
where word.StartsWith("f") || word.StartsWith("w")
select word;
foreach (var word in found)
{
Console.WriteLine(word);
}
We apply two conditions in the where clause. We grab words that
start either with 'f' or 'w'.
$ dotnet run forest falcon war frost water
In the following example, we apply two conditions with &&.
var cars = new List<Car>
{
new ("Audi", 52642),
new ("Mercedes", 57127),
new ("Skoda", 9000),
new ("Volvo", 29000),
new ("Bentley", 350000),
new ("Citroen", 21000),
new ("Hummer", 41400),
new ("Volkswagen", 21600)
};
var res = from car in cars
where car.Price > 30000 && car.Price < 100000
select new { car.Name, car.Price };
foreach (var car in res)
{
Console.WriteLine($"{car.Name} {car.Price}");
}
record Car(string Name, int Price);
In the example, we filter the list of car objects with the where
clause. We include all cars whose price is between 30000 and 100000.
$ dotnet run Audi 52642 Mercedes 57127 Hummer 41400
C# query first/last
We get the first and the last element of an enumerable with First
and Last methods.
string[] words = { "falcon", "oak", "sky", "cloud", "tree", "tea", "water" };
var first = (from word in words
where word.Length == 3
select word).First();
Console.WriteLine(first);
var last = (from word in words
where word.Length == 3
select word).Last();
Console.WriteLine(last);
In the example, we access elements of an array.
var first = (from word in words
where word.Length == 3
select word).First();
We get the first element whose length is 3.
var last = (from word in words
where word.Length == 3
select word).Last();
We retrieve the last element with length 3.
$ dotnet run oak tea
C# query select
The select clause projects each element of a sequence into a new
form. It selects, projects and transforms elements in a collection. The
select is usually called Map in other languages.
int[] vals = { 2, 4, 6, 8 };
var powered =
from val in vals
select Math.Pow(val, 2);
Console.WriteLine(string.Join(", ", powered));
string[] words = { "sky", "earth", "oak", "falcon" };
var wordLens =
from word in words
select word.Length;
Console.WriteLine(string.Join(", ", wordLens));
In the example, we transform an array of integers into a sequence of its powers and transform an array of words into a sequence of word lengths.
$ dotnet run 4, 16, 36, 64 3, 5, 3, 6
C# query select into anonymous type
A projection is a selection of specific fields from the returned objects.
Projections are performed with the select clause. We can project
fields into anonymous types.
User[] users =
{
new (1, "John", "London", "2001-04-01"),
new (2, "Lenny", "New York", "1997-12-11"),
new (3, "Andrew", "Boston", "1987-02-22"),
new (4, "Peter", "Prague", "1936-03-24"),
new (5, "Anna", "Bratislava", "1973-11-18"),
new (6, "Albert", "Bratislava", "1940-12-11"),
new (7, "Adam", "Trnava", "1983-12-01"),
new (8, "Robert", "Bratislava", "1935-05-15"),
new (9, "Robert", "Prague", "1998-03-14"),
};
var res = from user in users
where user.City == "Bratislava"
select new { user.Name, user.City };
Console.WriteLine(string.Join(", ", res));
record User(int id, string Name, string City, string DateOfBirth);
In the example, we select users who live in Bratislava.
var res = from user in users
where user.City == "Bratislava"
select new { user.Name, user.City };
With the select new clause, we create an anonymous type with two
fields: Name and City.
$ dotnet run
{ Name = Anna, City = Bratislava }, { Name = Albert, City = Bratislava }, { Name = Robert, City = Bratislava }
C# query flatten array
The following example flattens an array of arrays into a single array.
int[][] vals = {
new[] {1, 2, 3},
new[] {4},
new[] {5, 6, 6, 2, 7, 8},
};
var res = (from nested in vals
from e in nested
select e);
Console.WriteLine(string.Join(", ", res));
To flatten an array, we use two from clauses.
$ dotnet run 1, 2, 3, 4, 5, 6, 6, 2, 7, 8
C# query Concat
The Concat method concatenates two sequences.
User[] users1 =
{
new ("John", "Doe", "gardener"),
new ("Jane", "Doe", "teacher"),
new ("Roger", "Roe", "driver"),
};
User[] users2 =
{
new ("Peter", "Smith", "teacher"),
new ("Lucia", "Black", "accountant"),
new ("Michael", "Novak", "programmer"),
};
var users = (from u1 in users1 select u1).Concat(from u2 in users2 select u2);
foreach (var user in users)
{
Console.WriteLine(user);
}
record User(string FirstName, string LastName, string Occupation);
We have two arrays of users. We merge them with Concat.
$ dotnet run
User { FirstName = John, LastName = Doe, Occupation = gardener }
User { FirstName = Jane, LastName = Doe, Occupation = teacher }
User { FirstName = Roger, LastName = Roe, Occupation = driver }
User { FirstName = Peter, LastName = Smith, Occupation = teacher }
User { FirstName = Lucia, LastName = Black, Occupation = accountant }
User { FirstName = Michael, LastName = Novak, Occupation = programmer }
C# query cartesian product
Cartesian product is the multiplication of two sets to form the set of all ordered pairs.
char[] letters = "abcdefghi".ToCharArray();
char[] digits = "123456789".ToCharArray();
var coords =
from l in letters
from d in digits
select $"{l}{d}";
foreach (var coord in coords)
{
Console.Write($"{coord} ");
if (coord.EndsWith("9"))
{
Console.WriteLine();
}
}
Console.WriteLine();
In the example, we create a Cartesian product of letters and digits.
var coords =
from l in letters
from d in digits
select $"{l}{d}";
To accomplish the task, we use two from clauses.
$ dotnet run a1 a2 a3 a4 a5 a6 a7 a8 a9 b1 b2 b3 b4 b5 b6 b7 b8 b9 c1 c2 c3 c4 c5 c6 c7 c8 c9 d1 d2 d3 d4 d5 d6 d7 d8 d9 e1 e2 e3 e4 e5 e6 e7 e8 e9 f1 f2 f3 f4 f5 f6 f7 f8 f9 g1 g2 g3 g4 g5 g6 g7 g8 g9 h1 h2 h3 h4 h5 h6 h7 h8 h9 i1 i2 i3 i4 i5 i6 i7 i8 i9
C# query Count & Sum
The Count returns the number of elements in a sequence. The
Sum computes the sum of a sequence of numeric values.
var content =
@"Foxes are omnivorous mammals belonging to several genera
of the family Canidae. Foxes have a flattened skull, upright triangular ears,
a pointed, slightly upturned snout, and a long bushy tail. Foxes live on every
continent except Antarctica. By far the most common and widespread species of
fox is the red fox.";
var lines = content.Split("\n");
var n1 = (from line in lines
select line).Count();
Console.WriteLine($"There are {n1} lines");
var words = content.Split(" ");
var n2 = (from word in words
select word).Count();
Console.WriteLine($"There are {n2} words");
var chars = content.ToCharArray();
var n3 = (from c in chars
where c == 'f'
select c).Count();
Console.WriteLine($"There are {n3} f letters");
In the example, we use the Count method to count the number of
lines, words, and 'f' characters in the text.
$ dotnet run There are 5 lines There are 47 words There are 7 f letters
In the next example, we use the Sum method.
var vals = new List<int> { 1, -2, 3, -4, 5, 6, 7, -8 };
var s = (from x in vals where x > 0 select x).Sum();
Console.WriteLine($"The sum of positive values is: {s}");
var words = new List<string> { "falcon", "eagle", "hawk", "owl" };
int len = (from x in words select x.Length).Sum();
Console.WriteLine($"There are {len} letters in the list");
In the example, we sum the positive values in the vals list and
count the number of characters in the words list.
$ dotnet run The sum of positive values is: 22 There are 18 letters in the list
C# query Min, Max & Average
The Min and Max methods return the minimum and
maximum values of a sequence; the Average method computes the
average.
Car[] cars =
{
new ("Audi", 52642),
new ("Mercedes", 57127),
new ("Skoda", 9000),
new ("Volvo", 29000),
new ("Bentley", 350000),
new ("Citroen", 21000),
new ("Hummer", 41400),
new ("Volkswagen", 21600),
};
var prices = from car in cars
select car.Price;
Console.WriteLine($"Min: {prices.Min()}");
Console.WriteLine($"Max: {prices.Max()}");
Console.WriteLine($"Average: {prices.Average():F2}");
var cheapest = (from car in cars
orderby car.Price
select car).First();
Console.WriteLine($"The cheapest car: {cheapest.Name}");
record Car(string Name, int Price);
We compute statistics of car prices and find the cheapest car.
var cheapest = (from car in cars
orderby car.Price
select car).First();
The Min method returns only the lowest price. To get the whole
object, we sort the cars by their price and take the first one. Alternatively,
we can use the MinBy method.
$ dotnet run Min: 9000 Max: 350000 Average: 72721.12 The cheapest car: Skoda
C# query Any, All & Contains
The Any, All, and Contains methods are
quantifiers; they determine whether some or all elements of a sequence satisfy
a condition.
int[] vals = { 2, 4, 6, 8, 11, 12 };
bool anyOdd = (from val in vals
where val % 2 != 0
select val).Any();
Console.WriteLine($"Is there an odd value? {anyOdd}");
bool allPos = (from val in vals
select val).All(val => val > 0);
Console.WriteLine($"Are all values positive? {allPos}");
bool hasTen = (from val in vals
select val).Contains(10);
Console.WriteLine($"Does the array contain 10? {hasTen}");
The Any method returns true if the filtered sequence contains at
least one element. The All method checks whether all elements
satisfy the predicate and Contains checks whether the sequence
contains the given value. All three methods stop iterating as soon as the
result is known.
$ dotnet run Is there an odd value? True Are all values positive? True Does the array contain 10? False
C# query orderby
With the OrderBy method or the orderby clause we can
sort the elements of a sequence.
int[] vals = { 4, 5, 3, 2, 7, 0, 1, 6 };
var result = from e in vals
orderby e ascending
select e;
Console.WriteLine(string.Join(", ", result));
var result2 = from e in vals
orderby e descending
select e;
Console.WriteLine(string.Join(", ", result2));
In the example, we sort the integers in ascending and descending order. The
ascending keyword is optional.
$ dotnet run 0, 1, 2, 3, 4, 5, 6, 7 7, 6, 5, 4, 3, 2, 1, 0
In the next example, we sort objects by multiple fields.
var users = new List<User>
{
new ("Robert", "Novak", 1770),
new ("John", "Doe", 1230),
new ("Lucy", "Novak", 670),
new ("Ben", "Walter", 2050),
new ("Robin", "Brown", 2300),
new ("Amy", "Doe", 1250),
new ("Joe", "Draker", 1190),
new ("Janet", "Doe", 980),
new ("Peter", "Novak", 990),
new ("Albert", "Novak", 1930),
};
Console.WriteLine("sort descending by last name and salary");
var sortedUsers = from user in users
orderby user.LastName descending, user.Salary descending
select user;
foreach (var user in sortedUsers)
{
Console.WriteLine(user);
}
Console.WriteLine("-------------------------");
Console.WriteLine("sort ascending by last name and salary");
var sortedUsers2 = from user in users
orderby user.LastName ascending, user.Salary ascending
select user;
foreach (var user in sortedUsers2)
{
Console.WriteLine(user);
}
record User(string FirstName, string LastName, int Salary);
In the example, we sort the users first by their last names, then by their salaries.
var sortedUsers = from user in users
orderby user.LastName descending, user.Salary descending
select user;
Here, we sort the users by their last names and then by their salaries in descending order.
var sortedUsers2 = from user in users
orderby user.LastName ascending, user.Salary ascending
select user;
Here, we sort the users by their last names and then by their salaries in ascending order.
$ dotnet run
sort descending by last name and salary
User { FirstName = Ben, LastName = Walter, Salary = 2050 }
User { FirstName = Albert, LastName = Novak, Salary = 1930 }
User { FirstName = Robert, LastName = Novak, Salary = 1770 }
User { FirstName = Peter, LastName = Novak, Salary = 990 }
User { FirstName = Lucy, LastName = Novak, Salary = 670 }
User { FirstName = Joe, LastName = Draker, Salary = 1190 }
User { FirstName = Amy, LastName = Doe, Salary = 1250 }
User { FirstName = John, LastName = Doe, Salary = 1230 }
User { FirstName = Janet, LastName = Doe, Salary = 980 }
User { FirstName = Robin, LastName = Brown, Salary = 2300 }
-------------------------
sort ascending by last name and salary
User { FirstName = Robin, LastName = Brown, Salary = 2300 }
User { FirstName = Janet, LastName = Doe, Salary = 980 }
User { FirstName = John, LastName = Doe, Salary = 1230 }
User { FirstName = Amy, LastName = Doe, Salary = 1250 }
User { FirstName = Joe, LastName = Draker, Salary = 1190 }
User { FirstName = Lucy, LastName = Novak, Salary = 670 }
User { FirstName = Peter, LastName = Novak, Salary = 990 }
User { FirstName = Robert, LastName = Novak, Salary = 1770 }
User { FirstName = Albert, LastName = Novak, Salary = 1930 }
User { FirstName = Ben, LastName = Walter, Salary = 2050 }
C# query Reverse
The Reverse method inverts the order of the elements in a sequence.
(Note that this is not the same as sorting in descending order.)
int[] vals = { 1, 3, 6, 0, -1, 2, 9, 9, 8 };
var reversed = (from val in vals select val).Reverse();
Console.WriteLine(string.Join(", ", reversed));
In the example, we reverse the elements of an array.
$ dotnet run 8, 9, 9, 2, -1, 0, 6, 3, 1
C# query Skip & Take
The Skip method bypasses a specified number of elements and the
Take method returns a specified number of elements. Together they
can be used for paging.
string[] words = { "sky", "forest", "falcon", "oak", "water", "war",
"cloud", "frost", "rock", "eagle", "blue" };
int pageSize = 4;
int pages = (int) Math.Ceiling(words.Length / (double) pageSize);
for (int page = 0; page < pages; page++)
{
var items = (from word in words
orderby word
select word).Skip(page * pageSize).Take(pageSize);
Console.WriteLine($"Page {page + 1}: {string.Join(", ", items)}");
}
We sort the words and split them into pages of four words.
.Skip(page * pageSize).Take(pageSize);
For each page, we skip the words of the previous pages and take the next four words. The last page contains the remaining three words.
$ dotnet run Page 1: blue, cloud, eagle, falcon Page 2: forest, frost, oak, rock Page 3: sky, war, water
C# LINQ group by
We can group data into categories based on a certain key.
var cars = new List<Car>
{
new ("Audi", "red", 52642),
new ("Mercedes", "blue", 57127),
new ("Skoda", "black", 9000),
new ("Volvo", "red", 29000),
new ("Bentley", "yellow", 350000),
new ("Citroen", "white", 21000),
new ("Hummer", "black", 41400),
new ("Volkswagen", "white", 21600),
};
var groups = from car in cars
group car by car.Colour;
foreach (var group in groups)
{
Console.WriteLine(group.Key);
foreach (var car in group)
{
Console.WriteLine($" {car.Name} {car.Price}");
}
}
record Car(string Name, string Colour, int Price);
In the example, we separate the available cars into groups by their colour.
$ dotnet run red Audi 52642 Volvo 29000 blue Mercedes 57127 black Skoda 9000 Hummer 41400 yellow Bentley 350000 white Citroen 21000 Volkswagen 21600
In the following example, we perform grouping and aggregation operations.
Revenue[] revenues =
{
new (1, "Q1", 2340),
new (2, "Q1", 1200),
new (3, "Q1", 980),
new (4, "Q2", 340),
new (5, "Q2", 780),
new (6, "Q3", 2010),
new (7, "Q3", 3370),
new (8, "Q4", 540),
};
var res = from revenue in revenues
group revenue by revenue.Quarter
into g
select new { Quarter = g.Key, Total = g.Sum(e => e.Amount) };
foreach (var line in res)
{
Console.WriteLine(line);
}
record Revenue(int Id, string Quarter, int Amount);
We have revenues for four quarters. We group the revenues by the quarters and sum the amounts.
$ dotnet run
{ Quarter = Q1, Total = 4520 }
{ Quarter = Q2, Total = 1120 }
{ Quarter = Q3, Total = 5380 }
{ Quarter = Q4, Total = 540 }
We can apply a filter on aggregated data with where clause.
Revenue[] revenues =
{
new (1, "Q1", 2340),
new (2, "Q1", 1200),
new (3, "Q1", 980),
new (4, "Q2", 340),
new (5, "Q2", 780),
new (6, "Q3", 2010),
new (7, "Q3", 3370),
new (8, "Q4", 540),
};
var res = from revenue in revenues
group revenue by revenue.Quarter
into g
where g.Count() == 2
select new { Quarter = g.Key, Total = g.Sum(c => c.Amount) };
foreach (var line in res)
{
Console.WriteLine(line);
}
record Revenue(int Id, string Quarter, int Amount);
In the example, we pick only those quarters, which have exactly two revenues.
$ dotnet run
{ Quarter = Q2, Total = 1120 }
{ Quarter = Q3, Total = 5380 }
We can group by multiple keys with an anonymous type.
User[] users =
{
new ("John", "Bratislava", "programmer"),
new ("Jane", "Prague", "teacher"),
new ("Roger", "Bratislava", "programmer"),
new ("Lucia", "Bratislava", "teacher"),
new ("Peter", "Prague", "teacher"),
new ("Adam", "Bratislava", "programmer"),
};
var res = from user in users
group user by new { user.City, user.Occupation } into g
orderby g.Key.City, g.Key.Occupation
select new { g.Key.City, g.Key.Occupation, Count = g.Count() };
foreach (var e in res)
{
Console.WriteLine($"{e.City}, {e.Occupation}: {e.Count}");
}
record User(string Name, string City, string Occupation);
We count users by their city and occupation.
group user by new { user.City, user.Occupation } into g
orderby g.Key.City, g.Key.Occupation
The key of each group is an anonymous type with two properties. Anonymous types compare by value, so users with the same city and occupation end up in the same group. We sort the groups by the properties of the key.
$ dotnet run Bratislava, programmer: 3 Bratislava, teacher: 1 Prague, teacher: 2
C# LINQ word frequency
In the next example, we count the frequency of words in a file. We use the King James Bible, which can be downloaded as a plain text file from Project Gutenberg.
using System.Text.RegularExpressions;
var fileName = "the-king-james-bible.txt";
var text = File.ReadAllText(fileName);
var matches = Regex.Matches(text, "[A-Za-z']+");
var topTen =
(from match in matches
group match.Value by match.Value into wg
orderby wg.Count() descending
select new { Word = wg.Key, Total = wg.Count() })
.Take(10);
foreach (var e in topTen)
{
Console.WriteLine($"{e.Word}: {e.Total}");
}
We count the frequency of the words and print the ten most common ones.
var matches = Regex.Matches(text, "[A-Za-z']+");
We find all words with a regular expression. A word is a sequence of letters and apostrophes; digits and punctuation are not part of words.
group match.Value by match.Value into wg
orderby wg.Count() descending
select new { Word = wg.Key, Total = wg.Count() }
The words are grouped by their value; each group contains all occurrences of a
word. The groups are ordered by their size in descending order and projected
into an anonymous type. The MatchCollection can be used directly
in a query expression, because it implements
IEnumerable<Match>.
.Take(10);
We take the first ten most common words. Note that the comparison is case-sensitive; "the" and "The" are different words.
$ dotnet run the: 62313 and: 38914 of: 34643 to: 13494 And: 12846 that: 12589 in: 12389 shall: 9762 he: 9668 unto: 8942
C# LINQ join
The join clause joins sequences.
string[] basketA = { "coin", "book", "fork", "cord", "needle" };
string[] basketB = { "watches", "coin", "pen", "book", "pencil" };
var res = from item1 in basketA
join item2 in basketB
on item1 equals item2
select item1;
foreach (var item in res)
{
Console.WriteLine(item);
}
We have two arrays in the example. With the join clause, we find
all items that are present in both arrays.
$ dotnet run coin book
The coin and book words are included in both arrays.
C# LINQ group join
A join clause with the into keyword creates a group
join. Each element of the first sequence is paired with a collection of the
matching elements from the second sequence.
Department[] departments =
{
new (1, "Sales"),
new (2, "Engineering"),
new (3, "Marketing"),
};
Employee[] employees =
{
new ("John Doe", 1),
new ("Jane Smith", 2),
new ("Roger Roe", 2),
new ("Lucia Black", 1),
new ("Peter Novak", 2),
};
var res = from dep in departments
join emp in employees on dep.Id equals emp.DepartmentId into staff
select new { Department = dep.Name, Staff = staff };
foreach (var e in res)
{
Console.WriteLine($"{e.Department} ({e.Staff.Count()})");
foreach (var emp in e.Staff)
{
Console.WriteLine($" {emp.Name}");
}
}
record Department(int Id, string Name);
record Employee(string Name, int DepartmentId);
We list the employees of each department.
join emp in employees on dep.Id equals emp.DepartmentId into staff
The staff variable holds the employees of the current department.
Unlike the inner join, a group join includes also the departments that have no
employees; their collection is empty.
$ dotnet run Sales (2) John Doe Lucia Black Engineering (3) Jane Smith Roger Roe Peter Novak Marketing (0)
C# LINQ left outer join
A left outer join returns all elements of the first sequence, even if they have
no matching elements in the second sequence. In query syntax, it is created
with a group join and the DefaultIfEmpty method.
Department[] departments =
{
new (1, "Sales"),
new (2, "Engineering"),
new (3, "Marketing"),
};
Employee[] employees =
{
new ("John Doe", 1),
new ("Jane Smith", 2),
new ("Roger Roe", 2),
new ("Lucia Black", 1),
};
var res = from dep in departments
join emp in employees on dep.Id equals emp.DepartmentId into staff
from emp in staff.DefaultIfEmpty()
select new { Department = dep.Name, Employee = emp?.Name ?? "(none)" };
foreach (var e in res)
{
Console.WriteLine($"{e.Department}: {e.Employee}");
}
record Department(int Id, string Name);
record Employee(string Name, int DepartmentId);
We pair each department with its employees. The Marketing department has no employees, but it is still included in the output.
from emp in staff.DefaultIfEmpty()
select new { Department = dep.Name, Employee = emp?.Name ?? "(none)" };
The second from clause flattens the groups. For an empty group,
DefaultIfEmpty returns a sequence with a single default value,
which is null for reference types. Therefore, we use the
null-conditional and null-coalescing operators to access the name.
$ dotnet run Sales: John Doe Sales: Lucia Black Engineering: Jane Smith Engineering: Roger Roe Marketing: (none)
C# LINQ join on composite key
To join sequences on multiple values, we create anonymous types on both sides
of the equals keyword.
Price[] prices =
{
new ("apple", "Bratislava", 1.2m),
new ("apple", "Prague", 1.1m),
new ("pear", "Bratislava", 1.5m),
new ("pear", "Prague", 1.7m),
};
Order[] orders =
{
new ("apple", "Prague", 10),
new ("pear", "Bratislava", 4),
new ("pear", "Prague", 6),
};
var res = from order in orders
join price in prices
on new { order.Product, order.City } equals new { price.Product, price.City }
select new { order.Product, order.City, Total = order.Quantity * price.Amount };
foreach (var e in res)
{
Console.WriteLine($"{e.Product} in {e.City}: {e.Total}");
}
record Price(string Product, string City, decimal Amount);
record Order(string Product, string City, int Quantity);
The price of a product depends on the city. We match orders with prices by both the product and the city and compute the total for each order.
The anonymous types must have the same property names in the same order; otherwise, the code does not compile.
$ dotnet run apple in Prague: 11.0 pear in Bratislava: 6.0 pear in Prague: 10.2
C# query conversions
We can transform the returned enumerable into a list, array, or dictionary.
User[] users =
{
new (1, "John", "London", "2001-04-01"),
new (2, "Lenny", "New York", "1997-12-11"),
new (3, "Andrew", "Boston", "1987-02-22"),
new (4, "Peter", "Prague", "1936-03-24"),
new (5, "Anna", "Bratislava", "1973-11-18"),
new (6, "Albert", "Bratislava", "1940-12-11"),
new (7, "Adam", "Trnava", "1983-12-01"),
new (8, "Robert", "Bratislava", "1935-05-15"),
new (9, "Robert", "Prague", "1998-03-14"),
};
string[] cities = (from user in users
select user.City).Distinct().ToArray();
Console.WriteLine(string.Join(", ", cities));
Console.WriteLine("------------");
List<User> inBratislava = (from user in users
where user.City == "Bratislava"
select user).ToList();
foreach (var user in inBratislava)
{
Console.WriteLine(user);
}
Console.WriteLine("------------");
Dictionary<int, string> userIds =
(from user in users
select user).ToDictionary(user => user.id, user => user.Name);
foreach (var kvp in userIds)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
record User(int id, string Name, string City, string DateOfBirth);
We execute three queries on our data source; the resulting enumerable is transformed into a list, array, and dictionary.
string[] cities = (from user in users
select user.City).Distinct().ToArray();
In this query, we select all cities from the data source. We apply the
Distinct method and finally call the ToArray method.
List<User> inBratislava = (from user in users
where user.City == "Bratislava"
select user).ToList();
Here we get a list of users who live in Bratislava; we call the ToList
method.
Dictionary<int, string> userIds =
(from user in users
select user).ToDictionary(user => user.id, user => user.Name);
In this query, we turn the user names and their ids into a dictionary.
$ dotnet run
London, New York, Boston, Prague, Bratislava, Trnava
------------
User { id = 5, Name = Anna, City = Bratislava, DateOfBirth = 1973-11-18 }
User { id = 6, Name = Albert, City = Bratislava, DateOfBirth = 1940-12-11 }
User { id = 8, Name = Robert, City = Bratislava, DateOfBirth = 1935-05-15 }
------------
1: John
2: Lenny
3: Andrew
4: Peter
5: Anna
6: Albert
7: Adam
8: Robert
9: Robert
C# query XML
LINQ can be used to process XML with the LINQ to XML API from the
System.Xml.Linq namespace.
using System.Xml.Linq;
var xml = """
<Users>
<User>
<Name>Jack Moore</Name>
<Occupation>programmer</Occupation>
</User>
<User>
<Name>Paul Novak</Name>
<Occupation>driver</Occupation>
</User>
<User>
<Name>Frank Woody</Name>
<Occupation>teacher</Occupation>
</User>
<User>
<Name>Martina Doe</Name>
<Occupation>programmer</Occupation>
</User>
<User>
<Name>Lucia Black</Name>
<Occupation>teacher</Occupation>
</User>
</Users>
""";
var xdoc = XDocument.Parse(xml);
var teachers = from u in xdoc.Descendants("User")
where (string?) u.Element("Occupation") == "teacher"
select (string?) u.Element("Name");
foreach (var name in teachers)
{
Console.WriteLine(name);
}
We parse the XML data and select the names of all teachers.
var teachers = from u in xdoc.Descendants("User")
where (string?) u.Element("Occupation") == "teacher"
select (string?) u.Element("Name");
The Descendants method returns all User elements of
the document. The explicit conversion of an XElement to
string? returns the text of the element, or null if
the element does not exist.
$ dotnet run Frank Woody Lucia Black
C# query list directory contents
The Directory.EnumerateFiles method returns an enumerable
collection of file names that meet specified criteria.
var path = ".";
var files = (from file in Directory.EnumerateFiles(path, "*.txt", SearchOption.AllDirectories)
where Path.GetFileName(file).Contains("data", StringComparison.OrdinalIgnoreCase)
orderby file
select file).ToList();
foreach (var file in files)
{
Console.WriteLine(file);
}
Console.WriteLine($"{files.Count} files found");
The example recursively searches the current directory for all text files whose names contain the word data, regardless of the case.
select file).ToList();
We materialize the query with ToList. Otherwise, the directory
tree would be searched twice: once in the foreach loop and once
when we count the files.
$ dotnet run ./data.txt ./docs/2026/DATA-backup.txt ./docs/sales_data.txt 3 files found
C# query let clause
The let clause allows us to store the result of a sub-expression in
order to use it in subsequent clauses.
John Doe, gardener, 12/5/1997 Jane Doe, teacher, 5/16/1983 Robert Smith, driver, 4/2/2001 Maria Smith, cook, 9/21/1976
These are the contents of the data.csv file.
using System.Text;
var path = "data.csv";
var lines = File.ReadLines(path, Encoding.UTF8);
var users = from line in lines
let fields = line.Replace(", ", ",").Split(",")
select new User(fields[0], fields[1], DateTime.Parse(fields[2]));
var sorted = from user in users
orderby user.DateOfBirth descending
select user;
foreach (var user in sorted)
{
Console.WriteLine(user);
}
public record User(string Name, string Occupation, DateTime DateOfBirth);
In the example, we parse the data.csv file and create a sequence
of users; the users are sorted by their date of birth in descending order.
var users = from line in lines
let fields = line.Replace(", ", ",").Split(",")
select new User(fields[0], fields[1], DateTime.Parse(fields[2]));
In the first query expression, we split a line into its fields; the fields are
stored in the fields variable which is later used in the
select clause.
$ dotnet run
User { Name = Robert Smith, Occupation = driver, DateOfBirth = 4/2/2001 12:00:00 AM }
User { Name = John Doe, Occupation = gardener, DateOfBirth = 12/5/1997 12:00:00 AM }
User { Name = Jane Doe, Occupation = teacher, DateOfBirth = 5/16/1983 12:00:00 AM }
User { Name = Maria Smith, Occupation = cook, DateOfBirth = 9/21/1976 12:00:00 AM }
A query can contain multiple from, let, and
where clauses. In the next example, we find Pythagorean triples.
var triples = from a in Enumerable.Range(1, 20)
from b in Enumerable.Range(a, 20 - a + 1)
let c2 = a * a + b * b
let c = (int) Math.Sqrt(c2)
where c <= 20 && c * c == c2
select (a, b, c);
foreach (var (a, b, c) in triples)
{
Console.WriteLine($"{a}² + {b}² = {c}²");
}
We look for all triples of integers up to 20 that satisfy the equation a² + b² = c².
from a in Enumerable.Range(1, 20) from b in Enumerable.Range(a, 20 - a + 1)
The two from clauses generate all pairs of numbers. The second
range starts at a, so that we do not get duplicates such as
(3, 4) and (4, 3).
let c2 = a * a + b * b let c = (int) Math.Sqrt(c2) where c <= 20 && c * c == c2
The two let clauses compute the square of the hypotenuse and its
integer square root. The where clause keeps only the pairs whose
sum of squares is a perfect square. The result is projected into a tuple.
$ dotnet run 3² + 4² = 5² 5² + 12² = 13² 6² + 8² = 10² 8² + 15² = 17² 9² + 12² = 15² 12² + 16² = 20²
C# query continuation with into
The into keyword can be used after a select or
group clause to store the results in a new range variable and
continue the query. This is called a query continuation.
string[] words = { "sky", "Forest", "falcon", "oak", "Water", "war", "cloud", "Frost" };
var res = from word in words
select word.ToLower() into lower
where lower.StartsWith('f') || lower.StartsWith('w')
orderby lower
select lower;
Console.WriteLine(string.Join(", ", res));
We turn the words into lowercase and continue the query on the transformed values.
select word.ToLower() into lower
After the into keyword, the original word range
variable is out of scope; the rest of the query works with the
lower variable.
$ dotnet run falcon, forest, frost, war, water
C# query explicit range variable type
Non-generic collections such as ArrayList implement only
IEnumerable, so the compiler does not know the type of their
elements. We can specify the type of the range variable explicitly.
using System.Collections;
var vals = new ArrayList { 1, 2, 3, 4, 5, 6, 7 };
var res = from int val in vals
where val % 2 != 0
select val * val;
Console.WriteLine(string.Join(", ", res));
We select the squares of the odd values from an ArrayList.
from int val in vals
The explicitly typed range variable is translated into a call of the
Cast<int> method. If an element cannot be cast to the given
type, an InvalidCastException is thrown.
$ dotnet run 1, 9, 25, 49
C# query subquery
A query expression can contain another query expression. Such an inner query is called a subquery.
Student[] students =
{
new ("John", new[] { 3, 1, 2, 1 }),
new ("Jane", new[] { 1, 1, 2, 1 }),
new ("Roger", new[] { 4, 3, 2, 3 }),
new ("Lucia", new[] { 2, 1, 1, 2 }),
};
var res = from student in students
let avg = (from grade in student.Grades select grade).Average()
where avg < 2
orderby avg
select new { student.Name, Average = avg };
foreach (var e in res)
{
Console.WriteLine($"{e.Name}: {e.Average:F2}");
}
Console.WriteLine("------------");
var best = from student in students
where student.Grades.Average() ==
(from s in students select s.Grades.Average()).Min()
select student.Name;
Console.WriteLine($"The best student: {best.First()}");
record Student(string Name, int[] Grades);
We have students with their grades; 1 is the best grade. We select students with an average grade better than 2 and then the student with the best average.
let avg = (from grade in student.Grades select grade).Average()
For each student, the subquery computes the average of their grades. The
result is stored with the let clause.
where student.Grades.Average() ==
(from s in students select s.Grades.Average()).Min()
Here the subquery computes the best average of all students. Note that the subquery is evaluated for each student; for large collections, it is better to compute the value once before the query.
$ dotnet run Jane: 1.25 Lucia: 1.50 John: 1.75 ------------ The best student: Jane
Source
In this article we have worked with LINQ query expressions in C#.
Author
List all C# tutorials.