C# LINQ Method Syntax
Introduction
LINQ (Language Integrated Query) provides two main syntaxes for writing queries in C#: Query Syntax and Method Syntax. While Query Syntax resembles SQL, Method Syntax leverages extension methods to provide a fluent API for working with collections.
Method Syntax is often preferred by many C# developers due to its flexibility, extensibility, and consistency with the rest of the C# language's method-based paradigm. This tutorial will walk you through LINQ Method Syntax, from basic concepts to practical, real-world applications.
Understanding LINQ Method Syntax
LINQ Method Syntax (also called Fluent Syntax) uses extension methods defined in the System.Linq
namespace. These methods extend IEnumerable<T>
and related interfaces, allowing you to chain them together to create powerful data manipulation pipelines.
To use LINQ Method Syntax, you need to include the following namespace:
using System.Linq;
Basic LINQ Method Syntax Operations
Filtering Data with Where()
The Where()
method filters a sequence based on a predicate:
// Creating a list of numbers
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Using Where() to filter even numbers
IEnumerable<int> evenNumbers = numbers.Where(num => num % 2 == 0);
// Output: 2, 4, 6, 8, 10
Console.WriteLine("Even numbers:");
foreach (int num in evenNumbers)
{
Console.Write($"{num} ");
}
Transforming Data with Select()
The Select()
method transforms each element in a sequence:
// Using Select() to square each number
IEnumerable<int> squaredNumbers = numbers.Select(num => num * num);
// Output: 1, 4, 9, 16, 25, 36, 49, 64, 81, 100
Console.WriteLine("\nSquared numbers:");
foreach (int num in squaredNumbers)
{
Console.Write($"{num} ");
}
Ordering Data
LINQ provides methods to sort data:
// Using OrderBy() to sort in ascending order
IEnumerable<int> ascendingOrder = numbers.OrderBy(num => num);
// Using OrderByDescending() for descending order
IEnumerable<int> descendingOrder = numbers.OrderByDescending(num => num);
Console.WriteLine("\nDescending order:");
foreach (int num in descendingOrder)
{
Console.Write($"{num} ");
}
// Output: 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
Chaining LINQ Methods
One of the most powerful aspects of LINQ Method Syntax is the ability to chain methods together:
// Chain multiple operations: filter, transform, and sort
var result = numbers
.Where(n => n > 3) // Filter: numbers greater than 3
.Select(n => n * 2) // Transform: multiply by 2
.OrderByDescending(n => n); // Sort: descending order
// Output: 20, 18, 16, 14, 12, 10, 8
Console.WriteLine("\nChained operations result:");
foreach (int num in result)
{
Console.Write($"{num} ");
}
Aggregation Methods
LINQ provides methods to calculate aggregate values:
// Sum of all numbers
int sum = numbers.Sum();
Console.WriteLine($"\nSum: {sum}"); // Output: 55
// Average of all numbers
double average = numbers.Average();
Console.WriteLine($"Average: {average}"); // Output: 5.5
// Count of numbers greater than 5
int count = numbers.Count(n => n > 5);
Console.WriteLine($"Count of numbers > 5: {count}"); // Output: 5
// Maximum value
int max = numbers.Max();
Console.WriteLine($"Maximum value: {max}"); // Output: 10
// Minimum value
int min = numbers.Min();
Console.WriteLine($"Minimum value: {min}"); // Output: 1
Element Operations
LINQ provides methods to retrieve specific elements:
// First element
int first = numbers.First();
Console.WriteLine($"First element: {first}"); // Output: 1
// First element that matches a condition (or throws exception if none)
int firstEven = numbers.First(n => n % 2 == 0);
Console.WriteLine($"First even element: {firstEven}"); // Output: 2
// First element that matches a condition (or default value if none)
int firstLargeOrDefault = numbers.FirstOrDefault(n => n > 100);
Console.WriteLine($"First element > 100 or default: {firstLargeOrDefault}"); // Output: 0
// Single element that matches a condition (throws if not exactly one)
List<int> singleElementList = new List<int> { 42 };
int singleValue = singleElementList.Single();
Console.WriteLine($"Single element: {singleValue}"); // Output: 42
Working with Complex Objects
LINQ Method Syntax really shines when working with complex objects:
// Define a class to represent a product
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, Category = "Electronics" },
new Product { Id = 2, Name = "Mouse", Price = 25, Category = "Electronics" },
new Product { Id = 3, Name = "Desk", Price = 250, Category = "Furniture" },
new Product { Id = 4, Name = "Chair", Price = 150, Category = "Furniture" },
new Product { Id = 5, Name = "Keyboard", Price = 45, Category = "Electronics" },
new Product { Id = 6, Name = "Bookshelf", Price = 120, Category = "Furniture" }
};
// Find all electronics under $50
var affordableElectronics = products
.Where(p => p.Category == "Electronics" && p.Price < 50)
.OrderBy(p => p.Price);
Console.WriteLine("\nAffordable Electronics:");
foreach (var item in affordableElectronics)
{
Console.WriteLine($"{item.Name}: ${item.Price}");
}
// Output:
// Mouse: $25
// Keyboard: $45
// Group products by category and calculate average price per category
var categorySummary = products
.GroupBy(p => p.Category)
.Select(g => new {
Category = g.Key,
AveragePrice = g.Average(p => p.Price),
Count = g.Count()
});
Console.WriteLine("\nCategory Summary:");
foreach (var category in categorySummary)
{
Console.WriteLine($"{category.Category}: {category.Count} items, Avg Price: ${category.AveragePrice}");
}
// Output:
// Electronics: 3 items, Avg Price: $423.33
// Furniture: 3 items, Avg Price: $173.33
Practical Real-World Example: Data Analysis
Let's consider a more comprehensive example where we analyze sales data:
// Define classes for our data model
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
}
public class Order
{
public int Id { get; set; }
public int CustomerId { get; set; }
public DateTime OrderDate { get; set; }
public List<OrderItem> Items { get; set; } = new List<OrderItem>();
public decimal TotalAmount => Items.Sum(item => item.Price * item.Quantity);
}
public class OrderItem
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
// Create sample data
List<Customer> customers = new List<Customer>
{
new Customer { Id = 1, Name = "Alice Johnson", City = "New York" },
new Customer { Id = 2, Name = "Bob Smith", City = "Los Angeles" },
new Customer { Id = 3, Name = "Charlie Brown", City = "Chicago" },
new Customer { Id = 4, Name = "Diana Miller", City = "New York" }
};
List<Order> orders = new List<Order>
{
new Order
{
Id = 101, CustomerId = 1, OrderDate = new DateTime(2023, 1, 15),
Items = new List<OrderItem>
{
new OrderItem { ProductId = 1, ProductName = "Laptop", Quantity = 1, Price = 1200 },
new OrderItem { ProductId = 2, ProductName = "Mouse", Quantity = 1, Price = 25 }
}
},
new Order
{
Id = 102, CustomerId = 2, OrderDate = new DateTime(2023, 2, 3),
Items = new List<OrderItem>
{
new OrderItem { ProductId = 3, ProductName = "Desk", Quantity = 1, Price = 250 }
}
},
new Order
{
Id = 103, CustomerId = 1, OrderDate = new DateTime(2023, 2, 10),
Items = new List<OrderItem>
{
new OrderItem { ProductId = 4, ProductName = "Monitor", Quantity = 2, Price = 300 }
}
},
new Order
{
Id = 104, CustomerId = 3, OrderDate = new DateTime(2023, 3, 5),
Items = new List<OrderItem>
{
new OrderItem { ProductId = 1, ProductName = "Laptop", Quantity = 1, Price = 1200 },
new OrderItem { ProductId = 5, ProductName = "Keyboard", Quantity = 1, Price = 45 }
}
},
new Order
{
Id = 105, CustomerId = 4, OrderDate = new DateTime(2023, 3, 15),
Items = new List<OrderItem>
{
new OrderItem { ProductId = 3, ProductName = "Desk", Quantity = 1, Price = 250 },
new OrderItem { ProductId = 6, ProductName = "Chair", Quantity = 1, Price = 150 }
}
}
};
// Analysis 1: Find total sales per city
var salesByCity = customers
.Join(
orders,
customer => customer.Id,
order => order.CustomerId,
(customer, order) => new { customer.City, order.TotalAmount }
)
.GroupBy(x => x.City)
.Select(g => new {
City = g.Key,
TotalSales = g.Sum(x => x.TotalAmount)
})
.OrderByDescending(x => x.TotalSales);
Console.WriteLine("\nSales by City:");
foreach (var city in salesByCity)
{
Console.WriteLine($"{city.City}: ${city.TotalSales}");
}
// Output:
// New York: $1825
// Chicago: $1245
// Los Angeles: $250
// Analysis 2: Find customers with orders totaling more than $1000
var highValueCustomers = customers
.Where(c => orders
.Where(o => o.CustomerId == c.Id)
.Sum(o => o.TotalAmount) > 1000
)
.Select(c => new {
c.Name,
TotalPurchases = orders
.Where(o => o.CustomerId == c.Id)
.Sum(o => o.TotalAmount)
})
.OrderByDescending(x => x.TotalPurchases);
Console.WriteLine("\nHigh-Value Customers:");
foreach (var customer in highValueCustomers)
{
Console.WriteLine($"{customer.Name}: ${customer.TotalPurchases}");
}
// Output:
// Alice Johnson: $1525
// Charlie Brown: $1245
// Analysis 3: Find the most popular products (by quantity sold)
var popularProducts = orders
.SelectMany(o => o.Items)
.GroupBy(item => item.ProductName)
.Select(g => new {
ProductName = g.Key,
QuantitySold = g.Sum(item => item.Quantity),
Revenue = g.Sum(item => item.Price * item.Quantity)
})
.OrderByDescending(x => x.QuantitySold);
Console.WriteLine("\nPopular Products:");
foreach (var product in popularProducts)
{
Console.WriteLine($"{product.ProductName}: {product.QuantitySold} units, ${product.Revenue} revenue");
}
// Output:
// Laptop: 2 units, $2400 revenue
// Desk: 2 units, $500 revenue
// Monitor: 2 units, $600 revenue
// Mouse: 1 unit, $25 revenue
// Keyboard: 1 unit, $45 revenue
// Chair: 1 unit, $150 revenue
Advanced LINQ Method Features
Pagination with Skip and Take
Use Skip()
and Take()
for pagination:
// Create a large collection
var largeCollection = Enumerable.Range(1, 100);
// Get the second page (items 11-20) when showing 10 items per page
int pageSize = 10;
int pageNumber = 2;
var secondPage = largeCollection.Skip((pageNumber - 1) * pageSize).Take(pageSize);
Console.WriteLine("\nPage 2 (items 11-20):");
foreach (var item in secondPage)
{
Console.Write($"{item} ");
}
// Output: 11 12 13 14 15 16 17 18 19 20
Dynamic LINQ Queries with Expressions
For more complex scenarios, you can build LINQ expressions dynamically:
// Simple demonstration of dynamic filtering
string searchTerm = "Laptop";
decimal maxPrice = 1000;
// Build query based on conditions
IEnumerable<Product> filteredProducts = products;
if (!string.IsNullOrEmpty(searchTerm))
{
filteredProducts = filteredProducts.Where(p => p.Name.Contains(searchTerm));
}
if (maxPrice > 0)
{
filteredProducts = filteredProducts.Where(p => p.Price <= maxPrice);
}
// Execute the query
Console.WriteLine($"\nProducts matching criteria (name contains '{searchTerm}' AND price <= {maxPrice}):");
foreach (var product in filteredProducts)
{
Console.WriteLine($"{product.Name}: ${product.Price}");
}
// This would return no results since our Laptop is $1200
Common Patterns and Best Practices
Deferred Execution
LINQ queries are evaluated lazily by default. The query is not executed until you iterate over the results:
// Query is defined here
var query = numbers.Where(n => {
Console.WriteLine($"Evaluating: {n}");
return n % 2 == 0;
});
// Nothing is printed yet because the query hasn't executed
Console.WriteLine("\nExecuting query:");
// Query executes only when we iterate over the results
foreach (var item in query)
{
Console.WriteLine($"Result: {item}");
}
To force immediate execution, use methods like ToList()
, ToArray()
, or ToDictionary()
:
// Immediate execution with ToList()
var immediateResult = numbers.Where(n => n % 2 == 0).ToList();
Choosing Between Method Syntax and Query Syntax
Method Syntax is generally preferred when:
- You need operations that don't have a query syntax equivalent
- You're performing mainly method operations with minimal filtering and selection
- You prefer the fluent API style
Query Syntax is useful when:
- Your query is primarily about retrieving data (select/where operations)
- Readability is a top priority, especially for SQL developers
- You're using complex join or grouping operations
Many developers use a mix of both syntaxes depending on the specific needs.
Summary
LINQ Method Syntax provides a powerful way to query and transform data in C#. Key points to remember:
- Method Syntax uses extension methods from the
System.Linq
namespace - It allows for fluent chaining of operations
- Common operations include filtering, projection, sorting, and aggregation
- Method Syntax is particularly powerful for complex transformations and when working with custom objects
- LINQ queries use deferred execution unless you force immediate execution
- The syntax integrates naturally with the rest of C#'s method-based paradigm
With LINQ Method Syntax, you can write clean, expressive code that efficiently processes collections of any size or complexity.
Exercises
To practice your understanding of LINQ Method Syntax, try these exercises:
-
Create a collection of
Person
objects with properties forName
,Age
, andCity
. Use LINQ Method Syntax to:- Find all people aged 30 or older
- Group people by city and calculate the average age per city
- Find the oldest person in each city
-
Given a list of sentences, use LINQ Method Syntax to:
- Extract all unique words across all sentences
- Find the most common word that appears in the sentences
- Create a dictionary mapping each word to the count of sentences it appears in
-
Given sales data over time, use LINQ to:
- Calculate monthly totals
- Find the best-selling product by month
- Identify trends like month-over-month growth
Additional Resources
- Microsoft Documentation on LINQ
- 101 LINQ Samples
- LINQPad - A tool to interactively test LINQ queries
- More Than LINQ - A library that extends LINQ with additional operations
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)