LINQ (Language Integrated Query) is a powerful feature of .NET that allows you to query data sources such as collections, databases, and XML declaratively, like SQL. It is used naturally in C# and supports two syntaxes: method chain or query syntax.
Learning Objectives
- Use standard LINQ operators such as
Where,Select,OrderBy,GroupBy, andJoin. - Know the aggregate operators
Sum,Average,Max,Aggregate - Understand the difference between method chain and query expression.
- Know the meaning of deferred execution
Key concepts
1) Where and Select
int[] nums = [1, 2, 3, 4, 5, 6];
var result = nums.Where(n => n > 3).Select(n => n * n);
//Results: 16, 25, 36
Whereis filtered,Selectis converted.- Lambda (
=>) is a one-line function.
2) Sorting and grouping
var sorted = people.OrderBy(p => p.Age).ThenBy(p => p.Name);
var byCity = people.GroupBy(p => p.City);
foreach (var group in byCity)
{
Console.WriteLine($"{group.Key}: {group.Count()} people");
}
- In descending order:
OrderByDescending/ThenByDescending. - The result of
GroupByhas elements in the group together with the sequenceIGrouping<TKey, TElement>—Key.
3) Aggregation operators
| operator | Description |
|---|---|
Sum, Average, Max, Min, Count |
General statistics |
Aggregate(seed, (acc, x) => ...) |
Direct definition of cumulative expression |
Any, All |
Elements that satisfy the condition are Are-there-any/all-of-them |
4) Method chains vs query expressions
The same action can be used in two ways.
//method chain
var a = nums.Where(n => n > 3).Select(n => n * n);
//Query expression (SQL-like)
var b = from n in nums
where n > 3
select n * n;
- In simple cases, the method chain is short and clear.
- Query expressions such as
join,let, and complexgroup ... by ... intoare often easy to read.
5) Deferred execution
LINQ expressions are not executed immediately. It is only evaluated when iterating over foreach or when a termination operator such as ToList/ToArray is called.
var q = list.Where(n => n > 0);
list.Add(99);
foreach (var n in q) { /*99 is also included*/ }
Force evaluation to .ToList() to "snapshot" the original in place.
See as example
Example 1 — WhereSelect: Filtering and converting
int[] nums = [1, 2, 3, 4, 5, 6, 7, 8];
var squaresOfBig = nums.Where(n => n > 4).Select(n => n * n);
foreach (int x in squaresOfBig)
{
Console.WriteLine(x);
}
Execution result
25
36
49
64
Note: Where only passes the condition true, and Select converts each element to a different form. If you change the order, the results may change.
Example 2 — OrderByGroup: Sorting and grouping
Person[] people =
[
new("exponent", "Seoul", 25),
new("minho", "Busan", 30),
new("Seoyeon", "Seoul", 22),
new("Yoonjae", "Busan", 28),
];
var sorted = people.OrderBy(p => p.City).ThenByDescending(p => p.Age);
foreach (Person p in sorted)
{
Console.WriteLine($"{p.City} - {p.Name} ({p.Age})");
}
Console.WriteLine("---");
var groups = people.GroupBy(p => p.City);
foreach (var g in groups)
{
Console.WriteLine($"[{g.Key}] {g.Count()}name: {string.Join(", ", g.Select(p => p.Name))}");
}
public class Person(string name, string city, int age)
{
public string Name { get; } = name;
public string City { get; } = city;
public int Age { get; } = age;
}
Execution result
Busan - Minho (30)
Busan - Yoon Jae (28)
Seoul - Jisoo (25)
Seoul - Seoyeon (22)
---
[Seoul] 2 people: Jisoo, Seoyeon
[Busan] 2 people: Minho, Yoonjae
Memo: ThenBy is the secondary standard applied when the primary sorting standard is the same. IGrouping<K,T> has both Key properties and element sequences.
Example 3 — Aggregation: Aggregation operator
int[] nums = [10, 20, 30, 40, 50];
Console.WriteLine($"Sum: {nums.Sum()}");
Console.WriteLine($"Average: {nums.Average()}");
Console.WriteLine($"Maximum value: {nums.Max()}");
Console.WriteLine($"Minimum: {nums.Min()}");
Console.WriteLine($"Count: {nums.Count()}");
//Aggregate: Direct accumulation expression definition (here product)
int product = nums.Aggregate(1, (acc, n) => acc * n);
Console.WriteLine($"product: {product}");
bool allBigger = nums.All(n => n > 0);
bool anyEven = nums.Any(n => n % 2 == 0);
Console.WriteLine($"All positive numbers? {allBigger}, even existence? {anyEven}");
Execution result
Total: 150
Average: 30
Maximum: 50
Minimum: 10
Count: 5
Product: 12000000
All positive numbers? True, even numbers exist? True
Note: Aggregate(seed, func) cumulatively applies func to all elements starting from seed. Sum/Average etc. can be seen as special cases of Aggregate.
Example 4 — QuerySyntax: Method chain vs query expression
int[] nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
//method chain
var a = nums.Where(n => n % 2 == 0).Select(n => n * n);
//Query expression with the same meaning
var b = from n in nums
where n % 2 == 0
select n * n;
Console.WriteLine($"Method chain: [{string.Join(", ", a)}]");
Console.WriteLine($"Query expression: [{string.Join(", ", b)}]");
Execution result
Method chain: [4, 16, 36, 64, 100]
Query expression: [4, 16, 36, 64, 100]
Note: When compiled, both grammars result in the same code. You can choose according to your taste and readability. Even within the query expression, join, group ... by ... into, etc. can be freely combined.
Example 5 — DeferredExec: Delayed Execution Demonstration
List<int> nums = [1, 2, 3];
//Nothing to do at this point — just remember “what to do”
var query = nums.Where(n => n > 1);
nums.Add(99); //Modifying the original after query definition
Console.Write("Lazy execution results:");
foreach (int n in query)
{
Console.Write($"{n} ");
}
Console.WriteLine();
//Create a snapshot with ToList for immediate evaluation
var snapshot = nums.Where(n => n > 1).ToList();
nums.Add(100);
Console.WriteLine($"Snapshot result: [{string.Join(", ", snapshot)}]");
Execution result
Lazy execution result: 2 3 99
Snapshot result: [2, 3, 99]
Note: The reason the first result includes 99 is because at the moment foreach is actually executed, it looks at nums again. You can force the evaluation time with ToList()/ToArray().
Common Mistakes
Wherefollowed bySelect,Selectfollowed byWhere— Lambda parameter types vary depending on the order.- Forgetting delayed execution, run
foreachseveral times and re-evaluate each time → performance loss. If you want to rewrite the results, use.ToList(). - Confusion between
Countmethod andCountproperty:IEnumerable<T>is a method,List<T>is a property (Count). - Calling
First()/Single()on an empty sequence → Exception. To use safely, useFirstOrDefault(). - Trying to index
GroupByresults likeDictionary— needs to be converted toToDictionary(g => g.Key).
Cleanup
- LINQ is a standard tool for querying collections in SQL style.
Where/Select/OrderBy/GroupBy/Aggregateare most frequently used.- Method chain and query expression are the same function with different expressions.
- It is efficient thanks to lazy execution, but to prevent unexpected results, you can specify the evaluation time with
.ToList().
Try it yourself
cd src/WhereSelect
dotnet run
cd ../OrderByGroup
dotnet run
cd ../Aggregation
dotnet run
cd ../QuerySyntax
dotnet run
cd ../DeferredExec
dotnet run