LINQ to Objects
Introduction
LINQ to Objects is one of the most commonly used implementations of LINQ (Language Integrated Query) in C#. It enables you to query any collection that implements the IEnumerable
or IEnumerable<T>
interface directly in memory, using the same query syntax you would use for databases or XML.
With LINQ to Objects, you can:
- Filter data in collections
- Transform data from one format to another
- Sort collections in various ways
- Group elements by specific criteria
- Join multiple collections together
This makes working with collections in C# much more intuitive and powerful compared to traditional looping techniques.
Understanding LINQ to Objects
How It Works
LINQ to Objects operates directly on in-memory collections like arrays, lists, dictionaries, and any custom collection that implements IEnumerable<T>
. When you write a LINQ query against these collections:
- You're defining a query expression
- The query is executed when you enumerate the results (deferred execution)
- The results are returned as another
IEnumerable<T>
collection
Basic Syntax
LINQ to Objects provides two syntaxes:
Query Syntax:
var result = from item in collection
where item.Property > value
select item;
Method Syntax:
var result = collection
.Where(item => item.Property > value)
.Select(item => item);
Basic LINQ to Objects Operations
Filtering with Where
The Where
operator lets you filter elements based on a condition:
// Sample data
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Get even numbers
var evenNumbers = numbers.Where(n => n % 2 == 0);
// Output: 2, 4, 6, 8, 10
foreach (var num in evenNumbers)
{
Console.Write($"{num} ");
}
Projection with Select
Select
transforms each element into a new form:
// Sample data
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
// Square each number
var squares = numbers.Select(n => n * n);
// Output: 1, 4, 9, 16, 25
foreach (var square in squares)
{
Console.Write($"{square} ");
}
Ordering with OrderBy
and OrderByDescending
Sort elements in ascending or descending order:
// Sample data
List<string> fruits = new List<string>
{
"apple", "banana", "cherry", "date", "elderberry"
};
// Ascending order
var ascendingOrder = fruits.OrderBy(f => f);
// Descending order
var descendingOrder = fruits.OrderByDescending(f => f);
// Output: apple, banana, cherry, date, elderberry
Console.WriteLine("Ascending:");
foreach (var fruit in ascendingOrder)
{
Console.WriteLine(fruit);
}
// Output: elderberry, date, cherry, banana, apple
Console.WriteLine("\nDescending:");
foreach (var fruit in descendingOrder)
{
Console.WriteLine(fruit);
}
Intermediate Operations
Grouping with GroupBy
Group elements by a key:
// Sample data
List<Student> students = new List<Student>
{
new Student { Name = "Alice", Grade = "A" },
new Student { Name = "Bob", Grade = "B" },
new Student { Name = "Charlie", Grade = "A" },
new Student { Name = "Diana", Grade = "C" },
new Student { Name = "Eve", Grade = "B" }
};
// Group students by grade
var groupedByGrade = students.GroupBy(s => s.Grade);
// Output the groups
foreach (var group in groupedByGrade)
{
Console.WriteLine($"Grade {group.Key}:");
foreach (var student in group)
{
Console.WriteLine($" {student.Name}");
}
}
// Output:
// Grade A:
// Alice
// Charlie
// Grade B:
// Bob
// Eve
// Grade C:
// Diana
Joining with Join
Combine elements from two collections based on matching keys:
// Sample data
List<Department> departments = new List<Department>
{
new Department { Id = 1, Name = "HR" },
new Department { Id = 2, Name = "Engineering" },
new Department { Id = 3, Name = "Marketing" }
};
List<Employee> employees = new List<Employee>
{
new Employee { Name = "John", DepartmentId = 1 },
new Employee { Name = "Jane", DepartmentId = 2 },
new Employee { Name = "Bob", DepartmentId = 2 },
new Employee { Name = "Alice", DepartmentId = 3 },
new Employee { Name = "Charlie", DepartmentId = 4 } // No matching department
};
// Join employees with departments
var employeesWithDepartment = employees.Join(
departments,
employee => employee.DepartmentId,
department => department.Id,
(employee, department) => new {
EmployeeName = employee.Name,
DepartmentName = department.Name
}
);
// Output the joined data
foreach (var item in employeesWithDepartment)
{
Console.WriteLine($"{item.EmployeeName} works in {item.DepartmentName}");
}
// Output:
// John works in HR
// Jane works in Engineering
// Bob works in Engineering
// Alice works in Marketing
Set Operations
LINQ provides several set operations for collections:
// Sample data
int[] set1 = { 1, 2, 3, 4, 5 };
int[] set2 = { 3, 4, 5, 6, 7 };
// Distinct (removes duplicates)
int[] numbers = { 1, 2, 2, 3, 3, 3, 4, 5, 5 };
var distinct = numbers.Distinct();
// Output: 1, 2, 3, 4, 5
// Union (all unique elements from both sets)
var union = set1.Union(set2);
// Output: 1, 2, 3, 4, 5, 6, 7
// Intersect (elements present in both sets)
var intersect = set1.Intersect(set2);
// Output: 3, 4, 5
// Except (elements in first set but not in second)
var except = set1.Except(set2);
// Output: 1, 2
Aggregation Operations
LINQ provides various methods to perform calculations on collections:
// Sample data
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Count
int count = numbers.Count(); // 10
// Sum
int sum = numbers.Sum(); // 55
// Average
double average = numbers.Average(); // 5.5
// Min and Max
int min = numbers.Min(); // 1
int max = numbers.Max(); // 10
// Custom aggregations with Aggregate
int product = numbers.Aggregate(1, (result, item) => result * item);
// Output: 3628800 (product of all numbers)
Real-World Examples
Example 1: Processing Product Data
Let's say we have a list of products and want to find the expensive products in a specific category:
// Define the Product class
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
public int StockQuantity { get; set; }
}
// Create sample data
List<Product> products = new List<Product>
{
new Product { Id = 1, Name = "Laptop", Category = "Electronics", Price = 1200, StockQuantity = 15 },
new Product { Id = 2, Name = "Headphones", Category = "Electronics", Price = 250, StockQuantity = 30 },
new Product { Id = 3, Name = "Desk", Category = "Furniture", Price = 350, StockQuantity = 10 },
new Product { Id = 4, Name = "Chair", Category = "Furniture", Price = 120, StockQuantity = 25 },
new Product { Id = 5, Name = "Smartphone", Category = "Electronics", Price = 800, StockQuantity = 20 },
new Product { Id = 6, Name = "Bookshelf", Category = "Furniture", Price = 180, StockQuantity = 0 }
};
// Find expensive electronics (over $500) that are in stock
var expensiveElectronics = products
.Where(p => p.Category == "Electronics" && p.Price > 500 && p.StockQuantity > 0)
.OrderByDescending(p => p.Price);
Console.WriteLine("Expensive Electronics in stock:");
foreach (var product in expensiveElectronics)
{
Console.WriteLine($"- {product.Name}: ${product.Price} ({product.StockQuantity} in stock)");
}
// Output:
// Expensive Electronics in stock:
// - Laptop: $1200 (15 in stock)
// - Smartphone: $800 (20 in stock)
// Calculate total value of inventory by category
var inventoryValueByCategory = products
.GroupBy(p => p.Category)
.Select(g => new {
Category = g.Key,
TotalValue = g.Sum(p => p.Price * p.StockQuantity)
});
Console.WriteLine("\nInventory value by category:");
foreach (var category in inventoryValueByCategory)
{
Console.WriteLine($"{category.Category}: ${category.TotalValue}");
}
// Output:
// Inventory value by category:
// Electronics: $34500
// Furniture: $5300
Example 2: Processing Text Data
Let's analyze a text to find the most common words:
string text = @"
LINQ to Objects is a powerful feature in C# that allows you to query
in-memory collections. LINQ makes code more readable and expressive,
allowing developers to use the same query skills across different
data sources. LINQ is one of the most loved features in C#.";
// Split the text into words and count frequency
var wordFrequency = text.Split(new[] { ' ', '.', ',', '\r', '\n' },
StringSplitOptions.RemoveEmptyEntries)
.Select(word => word.ToLower())
.GroupBy(word => word)
.Select(group => new {
Word = group.Key,
Count = group.Count()
})
.OrderByDescending(x => x.Count)
.Take(5);
Console.WriteLine("5 most common words:");
foreach (var item in wordFrequency)
{
Console.WriteLine($"{item.Word}: {item.Count} occurrences");
}
// Output might be:
// to: 3 occurrences
// linq: 3 occurrences
// the: 2 occurrences
// in: 2 occurrences
// c#: 2 occurrences
Performance Considerations
While LINQ to Objects is powerful, there are some performance aspects to keep in mind:
- Deferred Execution: Most LINQ operations don't execute immediately but only when the result is enumerated
// Query defined but not executed yet
var query = numbers.Where(n => n > 5);
// Only now is the query actually executed
foreach (var num in query)
{
Console.WriteLine(num);
}
- Materialization: Sometimes you want to execute a query immediately using methods like
ToList()
,ToArray()
, orToDictionary()
// Immediately execute query and store results
var largeNumbers = numbers.Where(n => n > 5).ToList();
- Multiple Enumeration: Beware of enumerating the same query multiple times, as it will re-execute each time
// Bad practice - query gets executed twice
var query = numbers.Where(n => {
Console.WriteLine("Filtering...");
return n > 5;
});
Console.WriteLine("Count: " + query.Count()); // Executes query
Console.WriteLine("First: " + query.First()); // Executes query again
// Better practice
var result = numbers.Where(n => {
Console.WriteLine("Filtering...");
return n > 5;
}).ToList(); // Execute once
Console.WriteLine("Count: " + result.Count); // Uses stored results
Console.WriteLine("First: " + result[0]); // Uses stored results
Summary
LINQ to Objects is a powerful feature that simplifies working with collections in C#. It provides a consistent, expressive syntax for:
- Filtering data
- Transforming and projecting collections
- Sorting and ordering elements
- Grouping related items
- Joining multiple data sources
- Performing aggregate operations
By using LINQ to Objects, you can write more concise, readable code that's also less prone to errors than traditional iteration approaches.
Additional Resources
To deepen your understanding of LINQ to Objects:
- Microsoft's LINQ Documentation
- 101 LINQ Samples
- LINQPad - A tool for interactively testing LINQ queries
Exercise Suggestions
- Create a program to analyze a list of students, finding those with the highest GPA in each grade level
- Write a LINQ query to find duplicate elements in a collection
- Create a word-frequency analyzer for a text file using LINQ
- Implement a method that uses LINQ to join data from two different collections
- Write a LINQ query that finds the top 3 most expensive products in each product category
Happy coding with LINQ to Objects!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)