Skip to main content

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:

  1. You're defining a query expression
  2. The query is executed when you enumerate the results (deferred execution)
  3. The results are returned as another IEnumerable<T> collection

Basic Syntax​

LINQ to Objects provides two syntaxes:

Query Syntax:​

csharp
var result = from item in collection
where item.Property > value
select item;

Method Syntax:​

csharp
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:

csharp
// 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:

csharp
// 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:

csharp
// 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:

csharp
// 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:

csharp
// 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:

csharp
// 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:

csharp
// 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:

csharp
// 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:

csharp
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:

  1. Deferred Execution: Most LINQ operations don't execute immediately but only when the result is enumerated
csharp
// 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);
}
  1. Materialization: Sometimes you want to execute a query immediately using methods like ToList(), ToArray(), or ToDictionary()
csharp
// Immediately execute query and store results
var largeNumbers = numbers.Where(n => n > 5).ToList();
  1. Multiple Enumeration: Beware of enumerating the same query multiple times, as it will re-execute each time
csharp
// 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:

  1. Microsoft's LINQ Documentation
  2. 101 LINQ Samples
  3. LINQPad - A tool for interactively testing LINQ queries

Exercise Suggestions​

  1. Create a program to analyze a list of students, finding those with the highest GPA in each grade level
  2. Write a LINQ query to find duplicate elements in a collection
  3. Create a word-frequency analyzer for a text file using LINQ
  4. Implement a method that uses LINQ to join data from two different collections
  5. 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 feedback@compilenrun.com. I’d greatly appreciate your feedback! :)