Skip to main content

LINQ to Objects

Introduction

LINQ to Objects is a powerful implementation of Language Integrated Query (LINQ) that allows you to query and manipulate in-memory data collections like arrays, lists, and other collection types that implement IEnumerable<T>. With LINQ to Objects, you can write expressive, readable queries for filtering, sorting, grouping, and transforming data directly in your C# code.

Unlike other LINQ providers that work with external data sources (like databases or XML), LINQ to Objects operates on data that's already loaded into memory in your application. This makes it incredibly efficient for working with collection data and provides a consistent querying syntax across various data sources.

Getting Started with LINQ to Objects

To use LINQ to Objects, you'll need to:

  1. Add the required namespaces:
csharp
using System;
using System.Linq; // Required for LINQ operations
using System.Collections.Generic; // For collection types
  1. Have some collection data to work with:
csharp
// An array of integers
int[] numbers = { 5, 10, 8, 3, 6, 12 };

// A list of strings
List<string> fruits = new List<string> { "Apple", "Banana", "Cherry", "Date", "Fig" };

Let's dive into the core concepts and features of LINQ to Objects.

Basic Query Operations

Filtering with Where

The Where operator filters a collection based on a condition:

csharp
// Get only even numbers
var evenNumbers = numbers.Where(n => n % 2 == 0);

// Output: 10, 8, 6, 12
Console.WriteLine(string.Join(", ", evenNumbers));

Sorting with OrderBy/OrderByDescending

The OrderBy and OrderByDescending operators sort collections:

csharp
// Sort numbers in ascending order
var ascendingNumbers = numbers.OrderBy(n => n);

// Sort numbers in descending order
var descendingNumbers = numbers.OrderByDescending(n => n);

// Output: 3, 5, 6, 8, 10, 12
Console.WriteLine(string.Join(", ", ascendingNumbers));

// Output: 12, 10, 8, 6, 5, 3
Console.WriteLine(string.Join(", ", descendingNumbers));

Projection with Select

The Select operator transforms each element in a collection:

csharp
// Square each number
var squaredNumbers = numbers.Select(n => n * n);

// Output: 25, 100, 64, 9, 36, 144
Console.WriteLine(string.Join(", ", squaredNumbers));

// Get the length of each fruit name
var fruitNameLengths = fruits.Select(f => $"{f}: {f.Length} letters");

// Output: Apple: 5 letters, Banana: 6 letters, Cherry: 6 letters, Date: 4 letters, Fig: 3 letters
Console.WriteLine(string.Join(", ", fruitNameLengths));

Aggregation Operations

LINQ provides several aggregation methods to compute values from collections:

csharp
// Sum all numbers
int sum = numbers.Sum();
Console.WriteLine($"Sum: {sum}"); // Output: 44

// Find the average
double average = numbers.Average();
Console.WriteLine($"Average: {average}"); // Output: 7.33...

// Find minimum and maximum values
int min = numbers.Min();
int max = numbers.Max();
Console.WriteLine($"Min: {min}, Max: {max}"); // Output: Min: 3, Max: 12

// Count elements meeting a condition
int countGreaterThanFive = numbers.Count(n => n > 5);
Console.WriteLine($"Numbers greater than 5: {countGreaterThanFive}"); // Output: 4

Combining Multiple Operations

One of the most powerful aspects of LINQ is the ability to chain multiple operations:

csharp
// Get the top 3 even numbers in descending order
var top3EvenNumbers = numbers
.Where(n => n % 2 == 0) // Filter for even numbers
.OrderByDescending(n => n) // Sort in descending order
.Take(3); // Take only the first 3

// Output: 12, 10, 8
Console.WriteLine(string.Join(", ", top3EvenNumbers));

Query Syntax vs Method Syntax

LINQ provides two syntaxes for writing queries: query syntax and method syntax.

Method Syntax (Fluent Syntax)

The examples above use method syntax, which uses extension methods:

csharp
var evenNumbers = numbers.Where(n => n % 2 == 0);

Query Syntax

Query syntax looks more like SQL:

csharp
// Get only even numbers using query syntax
var evenNumbers = from n in numbers
where n % 2 == 0
select n;

// Output: 10, 8, 6, 12
Console.WriteLine(string.Join(", ", evenNumbers));

// More complex query with ordering
var sortedLongFruits = from f in fruits
where f.Length > 4
orderby f.Length
select f;

// Output: Apple, Banana, Cherry
Console.WriteLine(string.Join(", ", sortedLongFruits));

Both syntaxes are equivalent in capability, and you can choose whichever is more readable for your specific query.

Working with Complex Objects

LINQ to Objects truly shines when working with collections of complex objects:

csharp
// Define a simple class
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}

// Create a list of products
List<Product> products = new List<Product>
{
new Product { Id = 1, Name = "Laptop", Price = 1200.00m, Category = "Electronics" },
new Product { Id = 2, Name = "Desk Chair", Price = 250.50m, Category = "Furniture" },
new Product { Id = 3, Name = "Smartphone", Price = 800.00m, Category = "Electronics" },
new Product { Id = 4, Name = "Bookshelf", Price = 125.99m, Category = "Furniture" },
new Product { Id = 5, Name = "Headphones", Price = 150.00m, Category = "Electronics" }
};

// Find expensive electronic products
var expensiveElectronics = products
.Where(p => p.Category == "Electronics" && p.Price > 500)
.OrderBy(p => p.Price);

// Output: Smartphone ($800.00), Laptop ($1200.00)
foreach (var product in expensiveElectronics)
{
Console.WriteLine($"{product.Name} (${product.Price})");
}

Grouping and Joining

Grouping with GroupBy

The GroupBy operator groups collection elements by a key:

csharp
// Group products by category
var groupedByCategory = products.GroupBy(p => p.Category);

// Display products grouped by category
foreach (var group in groupedByCategory)
{
Console.WriteLine($"Category: {group.Key}");
foreach (var product in group)
{
Console.WriteLine($" {product.Name}: ${product.Price}");
}
}

// Output:
// Category: Electronics
// Laptop: $1200.00
// Smartphone: $800.00
// Headphones: $150.00
// Category: Furniture
// Desk Chair: $250.50
// Bookshelf: $125.99

Joining Collections

LINQ provides methods for joining multiple collections:

csharp
// Define a customer class
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}

// Define an order class
public class Order
{
public int OrderId { get; set; }
public int CustomerId { get; set; }
public decimal TotalAmount { get; set; }
}

// Sample data
List<Customer> customers = new List<Customer>
{
new Customer { Id = 1, Name = "Alice" },
new Customer { Id = 2, Name = "Bob" },
new Customer { Id = 3, Name = "Charlie" }
};

List<Order> orders = new List<Order>
{
new Order { OrderId = 101, CustomerId = 1, TotalAmount = 150.00m },
new Order { OrderId = 102, CustomerId = 2, TotalAmount = 75.50m },
new Order { OrderId = 103, CustomerId = 1, TotalAmount = 250.75m },
new Order { OrderId = 104, CustomerId = 3, TotalAmount = 125.25m }
};

// Join customers with their orders
var customerOrders = customers.Join(
orders,
customer => customer.Id,
order => order.CustomerId,
(customer, order) => new
{
CustomerName = customer.Name,
OrderId = order.OrderId,
Amount = order.TotalAmount
}
);

// Display the joined data
foreach (var item in customerOrders)
{
Console.WriteLine($"Customer: {item.CustomerName}, Order: {item.OrderId}, Amount: ${item.Amount}");
}

// Output:
// Customer: Alice, Order: 101, Amount: $150.00
// Customer: Alice, Order: 103, Amount: $250.75
// Customer: Bob, Order: 102, Amount: $75.50
// Customer: Charlie, Order: 104, Amount: $125.25

Deferred Execution

An important concept in LINQ is deferred execution. Many LINQ operations don't execute immediately but only when the result is actually needed:

csharp
// This query is defined but NOT executed yet
var query = numbers.Where(n => {
Console.WriteLine($"Testing: {n}");
return n % 2 == 0;
});

// Nothing has been printed yet

Console.WriteLine("About to execute query");

// Now the query executes as we enumerate the results
foreach (var number in query)
{
Console.WriteLine($"Even number: {number}");
}

// Output:
// About to execute query
// Testing: 5
// Testing: 10
// Even number: 10
// Testing: 8
// Even number: 8
// Testing: 3
// Testing: 6
// Even number: 6
// Testing: 12
// Even number: 12

If you want to execute a query immediately and store the results, use methods like ToList(), ToArray(), or ToDictionary():

csharp
// Execute query immediately and store results
var evenNumbersList = numbers.Where(n => n % 2 == 0).ToList();

// Now evenNumbersList contains the results, and the query won't execute again

Real-World Example: File System Operations

LINQ to Objects can help manage file operations:

csharp
// Get all .cs files in a directory, sorted by size
var codeFiles = new DirectoryInfo(@"C:\MyProject")
.GetFiles("*.cs", SearchOption.AllDirectories)
.OrderByDescending(f => f.Length)
.Select(f => new
{
Name = f.Name,
Directory = f.DirectoryName,
SizeKB = f.Length / 1024
});

// Display the 5 largest files
foreach (var file in codeFiles.Take(5))
{
Console.WriteLine($"{file.Name} - {file.SizeKB} KB");
}

Real-World Example: Data Analysis

LINQ makes data analysis tasks straightforward:

csharp
// Sample sales data
var sales = new List<(string Region, string Product, int Quarter, decimal Amount)>
{
("North", "Laptop", 1, 10000m),
("North", "Laptop", 2, 12000m),
("South", "Laptop", 1, 8000m),
("South", "Phone", 1, 5000m),
("North", "Phone", 2, 7500m),
("West", "Laptop", 1, 9000m),
("West", "Phone", 2, 6500m)
};

// Calculate total sales by region
var salesByRegion = sales
.GroupBy(s => s.Region)
.Select(g => new
{
Region = g.Key,
TotalSales = g.Sum(s => s.Amount)
})
.OrderByDescending(rs => rs.TotalSales);

foreach (var region in salesByRegion)
{
Console.WriteLine($"{region.Region}: ${region.TotalSales}");
}

// Calculate sales by product and quarter
var salesByProductAndQuarter = sales
.GroupBy(s => new { s.Product, s.Quarter })
.Select(g => new
{
Product = g.Key.Product,
Quarter = g.Key.Quarter,
TotalSales = g.Sum(s => s.Amount)
})
.OrderBy(s => s.Product)
.ThenBy(s => s.Quarter);

foreach (var item in salesByProductAndQuarter)
{
Console.WriteLine($"{item.Product} - Q{item.Quarter}: ${item.TotalSales}");
}

Summary

LINQ to Objects is a powerful feature in .NET that streamlines working with in-memory collections. It provides:

  • A consistent API for querying and manipulating collections
  • Expressive syntax for filtering, sorting, grouping, and transforming data
  • Support for method chaining for complex operations
  • Two syntax options to suit different coding styles
  • Deferred execution for better performance
  • A functional programming approach to data manipulation

By mastering LINQ to Objects, you can write more concise, readable, and maintainable code for working with collections. It eliminates the need for verbose loops and conditional statements, replacing them with declarative queries that clearly express your intent.

Additional Resources and Exercises

Resources:

Exercises:

  1. Basic Filtering: Create a list of integers and use LINQ to find all numbers divisible by both 3 and 5.

  2. Collection Transformation: Create a list of strings and use LINQ to transform it into a collection of objects containing the original string and its length.

  3. Complex Querying: Create a list of products with properties like Name, Price, and Category. Write LINQ queries to:

    • Find the most expensive product in each category
    • Calculate the average price of products by category
    • Find products with a price between two values
  4. Grouping Challenge: Using the same product list, group products by price ranges (0-50, 51-100, 101+) and display the count and average price in each group.

  5. Join Practice: Create two related collections (like students and courses) and use LINQ to join them to produce meaningful reports.

By practicing these exercises, you'll become proficient with LINQ to Objects and be able to apply it effectively in your real-world applications.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)