The Architect's city is growing fast, and now you need a powerful analytics dashboard to query building data. Enter LINQ (Language Integrated Query) — one of C#'s most iconic features. LINQ lets you query collections (and databases, XML, and more) using a clean, declarative syntax baked right into the language. No more messy loops to filter, sort, and aggregate data!
Two Syntaxes, One Power
LINQ offers two equivalent syntaxes. Method syntax (fluent) uses extension methods with lambdas. Query syntax looks like SQL. Both compile to the same code.
List<int> floors = new List<int> { 10, 30, 50, 20, 40 };
// Method syntax (more common in practice)
var tall = floors.Where(f => f > 25).OrderBy(f => f).ToList();
// Query syntax (SQL-like)
var tall2 = (from f in floors
where f > 25
orderby f
select f).ToList();
// Both produce: [30, 40, 50]
Essential LINQ Operators
LINQ lives in System.Linq. Here are the operators every Architect must know:
Where(predicate) — Filters elements matching a condition
Select(transform) — Projects/transforms each element into a new form
OrderBy(key) / OrderByDescending(key) — Sorts the sequence
First() / FirstOrDefault() — Returns the first element (or default if empty)
Any(predicate) — Returns true if any element matches
All(predicate) — Returns true if all elements match
Count() / Count(predicate) — Counts elements (optionally filtered)
GroupBy(key) — Groups elements by a key into IGrouping<TKey, T>
ToList() / ToArray() — Materializes the query into a concrete collection
Working with Objects
LINQ truly shines when querying collections of objects:
var buildings = new List<(string Name, int Floors, string Type, int Residents)>
{
("Sky Tower", 50, "Commercial", 200),
("Green Apt", 20, "Residential", 300),
("City Mall", 30, "Commercial", 150),
("Oak Villa", 10, "Residential", 100),
("Pine Court", 15, "Residential", 500)
};
// How many buildings are taller than 15 floors?
int tallCount = buildings.Count(b => b.Floors > 15);
Console.WriteLine($"Tall buildings: {tallCount}"); // 3
// Average number of floors
double avgFloors = buildings.Average(b => b.Floors);
Console.WriteLine($"Average floors: {avgFloors}"); // 25
// Find the tallest building
var tallest = buildings.OrderByDescending(b => b.Floors).First();
Console.WriteLine($"Tallest: {tallest.Name} ({tallest.Floors} floors)");
// Total residents across all buildings
int totalResidents = buildings.Sum(b => b.Residents);
Console.WriteLine($"Total residents: {totalResidents}"); // 1250
GroupBy — Organizing Data
GroupBy splits a collection into groups based on a key. Each group has a Key property and is itself an IEnumerable of matching elements:
var grouped = buildings.GroupBy(b => b.Type);
foreach (var group in grouped.OrderBy(g => g.Key))
{
Console.WriteLine($"{group.Key}: {group.Count()}");
}
// Commercial: 2
// Residential: 3
Deferred Execution — A Critical Concept ⚠️
Most LINQ operators use deferred execution — the query is not evaluated until you iterate over it (with foreach, ToList(), Count(), etc.). This means the data source can change between when you define the query and when you execute it:
List<int> numbers = new List<int> { 1, 2, 3 };
var query = numbers.Where(n => n > 1); // NOT executed yet!
numbers.Add(4); // Modify the source
foreach (int n in query) // NOW it executes, sees 2, 3, 4
Console.Write(n + " ");
Operators like ToList(), ToArray(), Count(), First(), Sum() force immediate execution. Use ToList() when you want to "snapshot" the results.
Time to build the city's analytics dashboard using LINQ, Architect!
📋 Instructions
**Build the City Analytics Dashboard with LINQ!**
Use the following building data (a list of tuples):
```
("Sky Tower", 50, "Commercial", 200)
("Green Apt", 20, "Residential", 300)
("City Mall", 30, "Commercial", 150)
("Oak Villa", 10, "Residential", 100)
("Pine Court", 15, "Residential", 500)
```
Each tuple has: `(Name, Floors, Type, Residents)`
1. Count buildings with more than 15 floors and print `"Tall buildings: "` + count
2. Calculate the average number of floors and print `"Average floors: "` + average (as integer)
3. Find the tallest building and print `"Tallest: {Name} ({Floors} floors)"`
4. Sum all residents and print `"Total residents: "` + sum
5. Group buildings by Type, then print `"Grouped: Commercial: {count}, Residential: {count}"` on one line
Use `.Count(b => ...)` for filtered counting, `.Average(b => b.Floors)` for the average (cast to int), `.OrderByDescending(b => b.Floors).First()` for the tallest, `.Sum(b => b.Residents)` for totals, and `.GroupBy(b => b.Type)` for grouping. For the grouped output, use `.ToDictionary(g => g.Key, g => g.Count())` to make it easy to access by key.