.NET LINQ Filtering
Introduction
Filtering is one of the most common operations when working with data collections. In traditional programming, filtering typically involves writing foreach
loops with conditional statements to extract elements that match specific criteria. LINQ (Language Integrated Query) provides a more elegant, readable, and concise way to filter collections in .NET applications.
LINQ's filtering capabilities allow you to express what data you want rather than how to retrieve it, making your code more declarative and often easier to understand. In this tutorial, we'll explore various ways to filter collections using LINQ in C#.
Basic Filtering with Where()
The most fundamental filtering method in LINQ is the Where()
extension method. It allows you to specify a predicate (a function that returns true
or false
) to determine which elements should be included in the result.
Syntax
// Method syntax
var result = collection.Where(item => condition);
// Query syntax
var result = from item in collection
where condition
select item;
Let's look at some practical examples.
Example 1: Filtering Numbers
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Method syntax
var evenNumbers = numbers.Where(n => n % 2 == 0);
// Query syntax
var oddNumbers = from n in numbers
where n % 2 != 0
select n;
Console.WriteLine("Even numbers:");
foreach (var num in evenNumbers)
{
Console.Write($"{num} "); // Output: 2 4 6 8 10
}
Console.WriteLine("\nOdd numbers:");
foreach (var num in oddNumbers)
{
Console.Write($"{num} "); // Output: 1 3 5 7 9
}
Example 2: Filtering Strings
List<string> fruits = new List<string>
{
"Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape"
};
// Filter fruits that start with a vowel
var fruitsStartingWithVowel = fruits.Where(f => "AEIOUaeiou".Contains(f[0]));
// Filter fruits with length greater than 5
var longFruits = from fruit in fruits
where fruit.Length > 5
select fruit;
Console.WriteLine("Fruits starting with vowels:");
foreach (var fruit in fruitsStartingWithVowel)
{
Console.WriteLine(fruit); // Output: Apple, Elderberry
}
Console.WriteLine("\nLong fruits (more than 5 characters):");
foreach (var fruit in longFruits)
{
Console.WriteLine(fruit); // Output: Banana, Cherry, Elderberry
}
Multiple Conditions in Filters
You can combine multiple conditions in your filter expressions using logical operators like &&
(AND) and ||
(OR).
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Numbers that are even AND greater than 5
var evenAndGreaterThan5 = numbers.Where(n => n % 2 == 0 && n > 5);
// Numbers that are divisible by 3 OR divisible by 5
var divisibleBy3Or5 = numbers.Where(n => n % 3 == 0 || n % 5 == 0);
Console.WriteLine("Even numbers greater than 5:");
foreach (var num in evenAndGreaterThan5)
{
Console.Write($"{num} "); // Output: 6 8 10
}
Console.WriteLine("\nNumbers divisible by 3 or 5:");
foreach (var num in divisibleBy3Or5)
{
Console.Write($"{num} "); // Output: 3 5 6 9 10
}
Filtering Complex Objects
LINQ's filtering capabilities really shine when working with collections of complex objects.
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Major { get; set; }
public double GPA { get; set; }
}
// Create a sample list of students
List<Student> students = new List<Student>
{
new Student { Id = 1, Name = "Alice", Age = 20, Major = "Computer Science", GPA = 3.8 },
new Student { Id = 2, Name = "Bob", Age = 22, Major = "Engineering", GPA = 3.2 },
new Student { Id = 3, Name = "Charlie", Age = 19, Major = "Math", GPA = 4.0 },
new Student { Id = 4, Name = "Diana", Age = 21, Major = "Computer Science", GPA = 3.5 },
new Student { Id = 5, Name = "Eve", Age = 20, Major = "Physics", GPA = 3.9 }
};
// Find CS students with GPA > 3.7
var highPerformingCSStudents = students.Where(s => s.Major == "Computer Science" && s.GPA > 3.7);
// Find students younger than 21 with a GPA of at least 3.5
var youngHighAchievers = from s in students
where s.Age < 21 && s.GPA >= 3.5
select s;
Console.WriteLine("High-performing CS students:");
foreach (var student in highPerformingCSStudents)
{
Console.WriteLine($"{student.Name} - GPA: {student.GPA}"); // Output: Alice - GPA: 3.8
}
Console.WriteLine("\nYoung high achievers:");
foreach (var student in youngHighAchievers)
{
Console.WriteLine($"{student.Name} - Age: {student.Age}, GPA: {student.GPA}");
// Output: Alice - Age: 20, GPA: 3.8
// Charlie - Age: 19, GPA: 4.0
// Eve - Age: 20, GPA: 3.9
}
Filtering with Index Information
Sometimes you need to access the index position of elements while filtering. The Where()
method has an overload that provides this functionality:
List<string> cities = new List<string>
{
"New York", "Los Angeles", "Chicago", "Houston", "Phoenix", "Philadelphia"
};
// Get even-indexed cities
var evenIndexedCities = cities.Where((city, index) => index % 2 == 0);
Console.WriteLine("Cities at even index positions:");
foreach (var city in evenIndexedCities)
{
Console.WriteLine(city); // Output: New York, Chicago, Phoenix
}
Advanced Filtering Techniques
Type Filtering with OfType()
The OfType<T>()
method filters elements based on their ability to be cast to a specified type.
ArrayList mixedList = new ArrayList { 1, "two", 3, "four", 5.0, "six" };
// Filter only strings
var onlyStrings = mixedList.OfType<string>();
// Filter only integers
var onlyIntegers = mixedList.OfType<int>();
Console.WriteLine("Strings in the collection:");
foreach (var item in onlyStrings)
{
Console.WriteLine(item); // Output: two, four, six
}
Console.WriteLine("\nIntegers in the collection:");
foreach (var item in onlyIntegers)
{
Console.WriteLine(item); // Output: 1, 3
}
Element Existence Testing
LINQ provides methods to test whether any or all elements satisfy a condition.
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
// Check if any number is greater than 3
bool anyGreaterThan3 = numbers.Any(n => n > 3);
// Check if all numbers are positive
bool allPositive = numbers.All(n => n > 0);
// Check if the list contains the number 6
bool contains6 = numbers.Contains(6);
Console.WriteLine($"Any number greater than 3? {anyGreaterThan3}"); // Output: True
Console.WriteLine($"All numbers positive? {allPositive}"); // Output: True
Console.WriteLine($"Contains 6? {contains6}"); // Output: False
Real-World Applications
Example 1: Filtering Product Data
Imagine you have an e-commerce application with a product catalog:
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; }
public bool IsDiscontinued { get; set; }
}
// Sample product catalog
List<Product> products = new List<Product>
{
new Product { Id = 1, Name = "Laptop", Category = "Electronics", Price = 1200.00M, StockQuantity = 15, IsDiscontinued = false },
new Product { Id = 2, Name = "Smartphone", Category = "Electronics", Price = 800.00M, StockQuantity = 25, IsDiscontinued = false },
new Product { Id = 3, Name = "Headphones", Category = "Electronics", Price = 150.00M, StockQuantity = 30, IsDiscontinued = false },
new Product { Id = 4, Name = "Coffee Maker", Category = "Home Appliances", Price = 75.00M, StockQuantity = 8, IsDiscontinued = false },
new Product { Id = 5, Name = "Blender", Category = "Home Appliances", Price = 50.00M, StockQuantity = 0, IsDiscontinued = true },
new Product { Id = 6, Name = "Desk Lamp", Category = "Home Decor", Price = 25.00M, StockQuantity = 12, IsDiscontinued = false }
};
// Find available electronics under $200
var affordableElectronics = products.Where(p => p.Category == "Electronics"
&& p.Price < 200
&& p.StockQuantity > 0
&& !p.IsDiscontinued);
// Find products that need restocking (less than 10 items in stock)
var lowStockProducts = from p in products
where p.StockQuantity < 10 && !p.IsDiscontinued
select p;
Console.WriteLine("Affordable electronics in stock:");
foreach (var product in affordableElectronics)
{
Console.WriteLine($"{product.Name} - ${product.Price}"); // Output: Headphones - $150.00
}
Console.WriteLine("\nLow stock products:");
foreach (var product in lowStockProducts)
{
Console.WriteLine($"{product.Name} - {product.StockQuantity} left"); // Output: Coffee Maker - 8 left
}
Example 2: Log Analysis
Here's an example of using LINQ to filter through application log entries:
public class LogEntry
{
public DateTime Timestamp { get; set; }
public string LogLevel { get; set; }
public string Component { get; set; }
public string Message { get; set; }
}
// Sample log entries
List<LogEntry> logs = new List<LogEntry>
{
new LogEntry { Timestamp = DateTime.Parse("2023-05-10 08:30:00"), LogLevel = "INFO", Component = "Authentication", Message = "User login successful" },
new LogEntry { Timestamp = DateTime.Parse("2023-05-10 08:35:15"), LogLevel = "ERROR", Component = "Database", Message = "Connection timeout" },
new LogEntry { Timestamp = DateTime.Parse("2023-05-10 09:15:22"), LogLevel = "WARNING", Component = "FileSystem", Message = "Low disk space" },
new LogEntry { Timestamp = DateTime.Parse("2023-05-10 10:05:41"), LogLevel = "INFO", Component = "Authentication", Message = "User logout" },
new LogEntry { Timestamp = DateTime.Parse("2023-05-10 11:20:30"), LogLevel = "ERROR", Component = "Network", Message = "API request failed" }
};
// Get all error logs
var errorLogs = logs.Where(log => log.LogLevel == "ERROR");
// Get authentication events within a specific time range
DateTime startTime = DateTime.Parse("2023-05-10 08:00:00");
DateTime endTime = DateTime.Parse("2023-05-10 10:00:00");
var authEvents = logs.Where(log => log.Component == "Authentication" &&
log.Timestamp >= startTime &&
log.Timestamp <= endTime);
Console.WriteLine("Error logs:");
foreach (var log in errorLogs)
{
Console.WriteLine($"{log.Timestamp}: {log.Component} - {log.Message}");
// Output: 2023-05-10 08:35:15: Database - Connection timeout
// 2023-05-10 11:20:30: Network - API request failed
}
Console.WriteLine("\nAuthentication events (8:00 AM - 10:00 AM):");
foreach (var log in authEvents)
{
Console.WriteLine($"{log.Timestamp}: {log.LogLevel} - {log.Message}");
// Output: 2023-05-10 08:30:00: INFO - User login successful
// 2023-05-10 10:05:41: INFO - User logout
}
Performance Considerations
When using LINQ filtering, keep these performance tips in mind:
-
Deferred Execution: LINQ queries use deferred execution, meaning they're not executed until you iterate over the results. This can be more efficient since elements are processed only when needed.
-
Early Filtering: Try to filter as early as possible to minimize the amount of data you're working with.
csharp// More efficient (filters first, then transforms)
var result = collection.Where(item => condition).Select(item => transform(item));
// Less efficient (transforms everything, then filters)
var result = collection.Select(item => transform(item)).Where(item => condition); -
ToList() or ToArray(): Use these methods to materialize your query results when you need to execute the query immediately or if you'll be iterating the results multiple times.
Common Filtering Methods Summary
Method | Description |
---|---|
Where() | Filters elements based on a predicate |
OfType<T>() | Filters elements based on type |
Any() | Checks if any element satisfies a condition |
All() | Checks if all elements satisfy a condition |
Contains() | Checks if collection contains a specific element |
First() , FirstOrDefault() | Returns the first element that satisfies a condition |
Last() , LastOrDefault() | Returns the last element that satisfies a condition |
Single() , SingleOrDefault() | Returns the only element that satisfies a condition |
Summary
LINQ's filtering capabilities provide a powerful, readable, and concise way to extract specific elements from collections. By using the Where()
method and related filtering techniques, you can express complex filtering logic in a declarative style that's both elegant and efficient.
Key points to remember:
- Use
Where()
for basic filtering operations - Combine conditions with logical operators (
&&
,||
) for complex filters - Consider specialized filtering methods like
OfType<T>()
for certain scenarios - Remember that LINQ queries use deferred execution
- Filter early in your query chains for better performance
With these techniques, you can write cleaner, more maintainable code while working with collections in your .NET applications.
Exercises
-
Create a list of integers and filter out all numbers that are not prime numbers.
-
Create a list of
Person
objects with properties for Name, Age, and Country. Write LINQ queries to:- Find all people older than 30
- Find people from a specific country
- Find people whose names start with a specific letter
-
Given a list of strings containing email addresses, write a LINQ query to filter valid email addresses (containing '@' and ending with a domain like ".com", ".org", etc.).
-
Create a collection of
Order
objects with properties for OrderId, CustomerName, OrderDate, and Total. Write LINQ queries to:- Find orders placed in the last month
- Find orders with a total greater than $1000
- Find the most recent order for a specific customer
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)