C# LINQ with Collections
Introduction
Language Integrated Query (LINQ) is one of the most powerful features in C#, allowing you to write expressive, declarative code to query and manipulate collections. Instead of writing complex loops with conditional logic, LINQ provides a clean, SQL-like syntax to filter, sort, group, join, and transform data in your collections.
In this tutorial, we'll explore how to use LINQ with various collection types in C# and how it can simplify your code while making it more readable and maintainable.
LINQ Basics
LINQ can be used with any collection that implements the IEnumerable<T>
interface, including arrays, lists, dictionaries, and more. There are two main syntaxes for writing LINQ queries:
- Query Syntax: Similar to SQL, using keywords like
from
,where
,select
- Method Syntax: Using extension methods like
Where()
,Select()
,OrderBy()
Let's start with some basic examples:
Setting Up Our Collections
First, let's create some collections that we'll use throughout this tutorial:
// A simple integer array
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// A list of products
List<Product> products = new List<Product>
{
new Product { Id = 1, Name = "Laptop", Price = 1200, Category = "Electronics" },
new Product { Id = 2, Name = "Desk Chair", Price = 250, Category = "Furniture" },
new Product { Id = 3, Name = "Smartphone", Price = 800, Category = "Electronics" },
new Product { Id = 4, Name = "Bookshelf", Price = 150, Category = "Furniture" },
new Product { Id = 5, Name = "Headphones", Price = 100, Category = "Electronics" },
new Product { Id = 6, Name = "Coffee Table", Price = 200, Category = "Furniture" }
};
// A simple dictionary
Dictionary<string, int> scores = new Dictionary<string, int>
{
{ "Alice", 95 },
{ "Bob", 87 },
{ "Charlie", 92 },
{ "Diana", 78 },
{ "Edward", 88 }
};
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
Basic LINQ Operations
Filtering with Where
Query Syntax:
// Get all even numbers
var evenNumbers = from num in numbers
where num % 2 == 0
select num;
Console.WriteLine("Even numbers:");
foreach (var num in evenNumbers)
{
Console.Write($"{num} ");
}
// Output: Even numbers: 2 4 6 8 10
Method Syntax:
// Get all even numbers
var evenNumbers = numbers.Where(num => num % 2 == 0);
Console.WriteLine("Even numbers:");
foreach (var num in evenNumbers)
{
Console.Write($"{num} ");
}
// Output: Even numbers: 2 4 6 8 10
Sorting with OrderBy
and OrderByDescending
// Sort products by price (ascending)
var sortedProducts = products.OrderBy(p => p.Price);
Console.WriteLine("\nProducts sorted by price (ascending):");
foreach (var product in sortedProducts)
{
Console.WriteLine($"{product.Name}: ${product.Price}");
}
// Output:
// Products sorted by price (ascending):
// Headphones: $100
// Bookshelf: $150
// Coffee Table: $200
// Desk Chair: $250
// Smartphone: $800
// Laptop: $1200
// Sort products by price (descending)
var expensiveFirst = products.OrderByDescending(p => p.Price);
Console.WriteLine("\nProducts sorted by price (descending):");
foreach (var product in expensiveFirst)
{
Console.WriteLine($"{product.Name}: ${product.Price}");
}
// Output:
// Products sorted by price (descending):
// Laptop: $1200
// Smartphone: $800
// Desk Chair: $250
// Coffee Table: $200
// Bookshelf: $150
// Headphones: $100
Projection with Select
The Select
operation allows you to transform the elements of a collection:
// Extract just the names of products
var productNames = products.Select(p => p.Name);
Console.WriteLine("\nProduct names:");
foreach (var name in productNames)
{
Console.WriteLine(name);
}
// Output:
// Product names:
// Laptop
// Desk Chair
// Smartphone
// Bookshelf
// Headphones
// Coffee Table
// Create anonymous objects with selected properties
var simplifiedProducts = products.Select(p => new { p.Name, p.Price });
Console.WriteLine("\nSimplified products:");
foreach (var item in simplifiedProducts)
{
Console.WriteLine($"{item.Name}: ${item.Price}");
}
// Output:
// Simplified products:
// Laptop: $1200
// Desk Chair: $250
// Smartphone: $800
// Bookshelf: $150
// Headphones: $100
// Coffee Table: $200
Combining LINQ Operations
LINQ operations can be chained together to create more complex queries:
// Get expensive electronics (price > 500) sorted by price
var expensiveElectronics = products
.Where(p => p.Category == "Electronics" && p.Price > 500)
.OrderBy(p => p.Price)
.Select(p => new { p.Name, p.Price });
Console.WriteLine("\nExpensive electronics:");
foreach (var item in expensiveElectronics)
{
Console.WriteLine($"{item.Name}: ${item.Price}");
}
// Output:
// Expensive electronics:
// Smartphone: $800
// Laptop: $1200
Quantification Operations
LINQ provides methods to check conditions across collections:
Any
and All
// Check if any product costs more than $1000
bool hasExpensiveProduct = products.Any(p => p.Price > 1000);
Console.WriteLine($"\nHas product over $1000: {hasExpensiveProduct}");
// Output: Has product over $1000: True
// Check if all products cost more than $50
bool allProductsAbove50 = products.All(p => p.Price > 50);
Console.WriteLine($"All products above $50: {allProductsAbove50}");
// Output: All products above $50: True
Aggregation Operations
LINQ provides various aggregation methods:
// Count items
int electronicsCount = products.Count(p => p.Category == "Electronics");
Console.WriteLine($"\nNumber of electronics: {electronicsCount}");
// Output: Number of electronics: 3
// Sum values
decimal totalProductsValue = products.Sum(p => p.Price);
Console.WriteLine($"Total value of all products: ${totalProductsValue}");
// Output: Total value of all products: $2700
// Average
decimal averagePrice = products.Average(p => p.Price);
Console.WriteLine($"Average product price: ${averagePrice}");
// Output: Average product price: $450
// Min and Max
decimal cheapestPrice = products.Min(p => p.Price);
decimal mostExpensivePrice = products.Max(p => p.Price);
Console.WriteLine($"Price range: ${cheapestPrice} - ${mostExpensivePrice}");
// Output: Price range: $100 - $1200
Grouping with LINQ
The GroupBy
operation allows you to group collection items by a key:
// Group products by category
var groupedByCategory = products.GroupBy(p => p.Category);
Console.WriteLine("\nProducts grouped by category:");
foreach (var group in groupedByCategory)
{
Console.WriteLine($"\n{group.Key} ({group.Count()} items):");
foreach (var product in group)
{
Console.WriteLine($" - {product.Name}: ${product.Price}");
}
}
// Output:
// Products grouped by category:
//
// Electronics (3 items):
// - Laptop: $1200
// - Smartphone: $800
// - Headphones: $100
//
// Furniture (3 items):
// - Desk Chair: $250
// - Bookshelf: $150
// - Coffee Table: $200
Working with Dictionaries
LINQ can also be used with dictionaries:
// Get students with scores >= 90
var highScorers = scores.Where(pair => pair.Value >= 90)
.Select(pair => pair.Key);
Console.WriteLine("\nStudents with high scores (>= 90):");
foreach (var student in highScorers)
{
Console.WriteLine(student);
}
// Output:
// Students with high scores (>= 90):
// Alice
// Charlie
Set Operations
LINQ provides set operations like Union
, Intersect
, and Except
:
int[] set1 = { 1, 2, 3, 4, 5 };
int[] set2 = { 4, 5, 6, 7, 8 };
// Union: combine unique elements from both sets
var unionResult = set1.Union(set2);
Console.WriteLine("\nUnion: " + string.Join(", ", unionResult));
// Output: Union: 1, 2, 3, 4, 5, 6, 7, 8
// Intersect: elements that appear in both sets
var intersectResult = set1.Intersect(set2);
Console.WriteLine("Intersect: " + string.Join(", ", intersectResult));
// Output: Intersect: 4, 5
// Except: elements in first set but not in second
var exceptResult = set1.Except(set2);
Console.WriteLine("Except: " + string.Join(", ", exceptResult));
// Output: Except: 1, 2, 3
Real-world Example: E-commerce Product Filtering
Let's build a more practical example simulating product filtering in an e-commerce application:
// More expanded product list
List<Product> inventory = new List<Product>
{
new Product { Id = 1, Name = "Gaming Laptop", Price = 1200, Category = "Electronics" },
new Product { Id = 2, Name = "Office Chair", Price = 250, Category = "Furniture" },
new Product { Id = 3, Name = "Smartphone", Price = 800, Category = "Electronics" },
new Product { Id = 4, Name = "Bookshelf", Price = 150, Category = "Furniture" },
new Product { Id = 5, Name = "Wireless Headphones", Price = 180, Category = "Electronics" },
new Product { Id = 6, Name = "Coffee Table", Price = 200, Category = "Furniture" },
new Product { Id = 7, Name = "Bluetooth Speaker", Price = 120, Category = "Electronics" },
new Product { Id = 8, Name = "Sofa", Price = 850, Category = "Furniture" },
new Product { Id = 9, Name = "Tablet", Price = 350, Category = "Electronics" },
new Product { Id = 10, Name = "Dining Table", Price = 400, Category = "Furniture" }
};
// User's filter settings
string categoryFilter = "Electronics";
decimal minPrice = 150;
decimal maxPrice = 500;
string sortBy = "price";
bool ascending = true;
// Apply filters using LINQ
var filteredProducts = inventory
.Where(p => p.Category == categoryFilter)
.Where(p => p.Price >= minPrice && p.Price <= maxPrice);
// Apply sorting
IEnumerable<Product> sortedFilteredProducts;
if (sortBy.ToLower() == "price")
{
sortedFilteredProducts = ascending
? filteredProducts.OrderBy(p => p.Price)
: filteredProducts.OrderByDescending(p => p.Price);
}
else // Sort by name
{
sortedFilteredProducts = ascending
? filteredProducts.OrderBy(p => p.Name)
: filteredProducts.OrderByDescending(p => p.Name);
}
// Display results
Console.WriteLine($"\nFiltered {categoryFilter} products (${minPrice} - ${maxPrice}):");
foreach (var product in sortedFilteredProducts)
{
Console.WriteLine($"{product.Name}: ${product.Price}");
}
// Output:
// Filtered Electronics products ($150 - $500):
// Wireless Headphones: $180
// Tablet: $350
Deferred Execution
An important concept to understand with LINQ is that many LINQ operations use deferred execution:
// Create a query but don't execute it yet
var query = numbers.Where(n => n % 2 == 0);
// At this point, nothing has been evaluated
// Add a new number to the source collection
int[] moreNumbers = numbers.Append(11).ToArray();
// Now the query is executed, including the new number
Console.WriteLine("\nEven numbers after modification:");
foreach (var num in query)
{
Console.Write($"{num} ");
}
// Output: Even numbers after modification: 2 4 6 8 10
// You can force immediate execution with methods like ToList(), ToArray(), Count(), etc.
var immediateResults = numbers.Where(n => n % 2 == 0).ToList();
Performance Considerations
While LINQ is convenient and powerful, it's important to be aware of potential performance implications:
- Avoid multiple iterations over the same data
- Use methods like
FirstOrDefault()
instead ofWhere().FirstOrDefault()
when appropriate - Be mindful of deferred execution, especially with database contexts
- For large collections, consider using
AsParallel()
for parallel processing
// Inefficient - multiple iterations
var count = products.Where(p => p.Price > 200).Count();
var expensive = products.Where(p => p.Price > 200).ToList();
// More efficient - iterate once
var expensiveProducts = products.Where(p => p.Price > 200).ToList();
var expensiveCount = expensiveProducts.Count;
// For very large collections, consider parallel LINQ
var parallelQuery = products.AsParallel().Where(p => p.Price > 200);
Summary
LINQ is an incredibly powerful feature that makes working with collections in C# much more expressive and concise. By using LINQ, you can:
- Filter collections with
Where
- Transform elements with
Select
- Sort with
OrderBy
andOrderByDescending
- Group with
GroupBy
- Perform aggregations with
Count
,Sum
,Average
,Min
,Max
- Check conditions with
Any
andAll
- Combine collections with set operations like
Union
andIntersect
LINQ works with any collection implementing IEnumerable<T>
, making it a universal tool for querying and manipulating data in your C# applications.
Exercises
-
Create a list of at least 10 people with properties for Name, Age, and City. Write LINQ queries to:
- Find all people above 30
- Group people by city and count them
- Find the average age per city
-
Create a list of orders with properties for OrderId, CustomerName, OrderDate, and Total. Write LINQ queries to:
- Find the top 3 highest value orders
- Group orders by customer and calculate their total spending
- Find orders from the last 30 days
-
Challenge: Create two lists representing students and their course enrollments. Write a LINQ query that joins these collections to find which students are taking specific courses.
Additional Resources
Remember that mastering LINQ takes practice, but the investment is well worth it as it will dramatically improve the clarity and conciseness of your code.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)