1. Basic Ways to Filter Collections
Filtering is like a gold sieve, but instead of nuggets, we pick out only the elements we need from a collection. For example, say we have a list of users — maybe we want to find only adults, or only students, or just those who love coffee (so, programmers basically). Picking the right elements is a super common task, everywhere: from working with DBs to handling user input.
Let's check out different ways to filter in C#. As we go, we'll keep building our little console app, adding more filters to it.
Manual Filtering with a Loop
Let's start with the simplest and most classic way: filtering with foreach:
// Example: we have a list of numbers, need to pick only the even ones
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
List<int> evenNumbers = new List<int>();
foreach (int number in numbers)
{
if (number % 2 == 0) // if the number is even
{
evenNumbers.Add(number);
}
}
Console.WriteLine("Even numbers:");
foreach (int n in evenNumbers)
{
Console.WriteLine(n);
}
This way always works, it's easy to get, but it's not the most compact or "modern" way.
Why Manual Filtering Isn't Always Handy?
When you get another collection, another filter criteria, or you need to write a few filters at once — you'll end up with a lot of similar code. You want it to be compact, readable, and modular.
2. Filtering by Complex Criteria
Say we have a collection of objects. Let's use the user example from our growing app.
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public bool IsStudent { get; set; }
}
Let's declare a list of users:
List<User> users = new List<User>
{
new User { Name = "Anya", Age = 17, IsStudent = true },
new User { Name = "Boris", Age = 21, IsStudent = false },
new User { Name = "Vika", Age = 19, IsStudent = true },
new User { Name = "Gleb", Age = 25, IsStudent = false }
};
Example 1: Pick All Students
List<User> students = new List<User>();
foreach (User user in users)
{
if (user.IsStudent)
students.Add(user);
}
Console.WriteLine("List of students:");
foreach (User user in students)
{
Console.WriteLine($"{user.Name} ({user.Age})");
}
Example 2: Pick Adult Students
List<User> adults = new List<User>();
foreach (User user in users)
{
if (user.Age >= 18 && user.IsStudent)
adults.Add(user);
}
Console.WriteLine("Adult students:");
foreach (User user in adults)
{
Console.WriteLine($"{user.Name} ({user.Age})");
}
What If Filter Criteria Are Dynamic?
You can move filter conditions into separate methods and call them inside the loop:
bool IsAdult(User u) { return u.Age >= 18; }
bool IsStudent(User u) { return u.IsStudent; }
List<User> filtered = new List<User>();
foreach (User user in users)
{
if (IsAdult(user) && IsStudent(user))
filtered.Add(user);
}
3. Filtering by Key in Dictionaries
Lists and arrays are cool, but we often work with dictionaries too.
Say we have a dictionary where the key is the user's name and the value is their age:
Dictionary<string, int> ageByName = new Dictionary<string, int>
{
["Anya"] = 17,
["Boris"] = 21,
["Vika"] = 19,
["Gleb"] = 25
};
Task: Pick Only Users 18+
Dictionary<string, int> adults = new Dictionary<string, int>();
foreach (var pair in ageByName)
{
if (pair.Value >= 18)
adults.Add(pair.Key, pair.Value);
}
Console.WriteLine("Adults:");
foreach (var pair in adults)
{
Console.WriteLine($"{pair.Key}: {pair.Value} years old");
}
Task: Pick Users Whose Names Start With "V"
Dictionary<string, int> namesWithV = new Dictionary<string, int>();
foreach (var pair in ageByName)
{
if (pair.Key.StartsWith("V"))
namesWithV.Add(pair.Key, pair.Value);
}
Console.WriteLine("Names starting with 'V':");
foreach (var pair in namesWithV)
{
Console.WriteLine($"{pair.Key}: {pair.Value} years old");
}
4. Filtering Principles
How the Filtering Process Works
┌──────────────────────────────────┐
│ List: [1, 2, 3, 4, 5, 6] │
└──────────────────────────────────┘
│
▼
[Check: n % 2 == 0]
│
▼
┌──────────────────────────────────┐
│ Result: [2, 4, 6] │
└──────────────────────────────────┘
Composing Filters: Filtering by Different Conditions
You can do sequential filtering if you want to pick data step by step:
// First, filter only students
List<User> onlyStudents = new List<User>();
foreach (User user in users)
{
if (user.IsStudent)
onlyStudents.Add(user);
}
// Then pick only adults among them
List<User> adultStudents = new List<User>();
foreach (User user in onlyStudents)
{
if (user.Age >= 18)
adultStudents.Add(user);
}
Or do it all at once:
List<User> adultStudents = new List<User>();
foreach (User user in users)
{
if (user.IsStudent && user.Age >= 18)
adultStudents.Add(user);
}
Sorting and Other Operations
You can sort using the Sort method for lists:
// Adult students sorted by age
adultStudents.Sort((a, b) => a.Age.CompareTo(b.Age));
We'll talk more about sorting in the next lecture!
5. Filtering in User Scenarios
1. Filtering User Input
Say our app takes a list of grades and needs to pick all the "fives".
Console.Write("Enter grades separated by space: ");
string input = Console.ReadLine();
List<int> grades = new List<int>();
foreach (string s in input.Split(' '))
{
if (int.TryParse(s, out int grade))
grades.Add(grade);
}
List<int> fives = new List<int>();
foreach (int grade in grades)
{
if (grade == 5)
fives.Add(grade);
}
Console.WriteLine("Fives:");
foreach (var grade in fives)
{
Console.WriteLine(grade);
}
2. Filtering by Multiple Conditions
Task: pick users who are students and aged from 18 to 22 inclusive.
List<User> filtered = new List<User>();
foreach (User user in users)
{
if (user.IsStudent && user.Age >= 18 && user.Age <= 22)
filtered.Add(user);
}
3. Filtering by Element Presence
Say we have a list of strings, and we want to pick only those that contain the substring ".net" (case-insensitive).
List<string> technologies = new List<string> { "C#", ".NET", "Java", "dotnet", "JavaScript" };
List<string> netTechs = new List<string>();
foreach (var tech in technologies)
{
if (tech.ToLower().Contains(".net"))
netTechs.Add(tech);
}
foreach (var tech in netTechs)
{
Console.WriteLine(tech);
}
6. Features and Typical Mistakes in Filtering
Filtering seems simple, but you can mess things up here. Let's break down some features.
Changing the Original Collection
If you filter the original collection and then change its contents after filtering — the result won't change if you already created a new list. But if you just remembered a reference to the old list, changes in the original collection can affect the result.
List<int> filtered = new List<int>();
foreach (int n in numbers)
{
if (n > 2)
filtered.Add(n);
}
// Let's change the original list
numbers.Add(10);
foreach (var n in filtered)
{
Console.WriteLine(n); // 10 won't show up here
}
Result of Manual Filtering
The result of manual filtering is usually a new mutable list (like List<T>), which you can do anything with — add, remove elements, etc.
Filtering and Performance
Filters in loops get called for every element in the collection, so if the function inside the condition is heavy — be careful. Sometimes it's easier to just use a simple foreach loop, especially if you have complex logic and need logging or extra actions.
GO TO FULL VERSION