Skip to main content

.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

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

csharp
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

csharp
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).

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

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

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

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

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

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

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

  1. 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.

  2. 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);
  3. 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

MethodDescription
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

  1. Create a list of integers and filter out all numbers that are not prime numbers.

  2. 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
  3. 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.).

  4. 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! :)