C#
IEnumerable
case-insensitive search
string comparison
.NET programming

How to make IEnumerablestring.Contains case-insensitive?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

A case-insensitive membership check on IEnumerable<string> looks simple, but the best solution depends on how often you perform the lookup and what kind of text you are comparing. In .NET, the important choice is not just "ignore case," but which comparer semantics you want.

The Simple One-Off Query

For an occasional lookup, Any with an explicit string comparison is the most direct answer.

csharp
1using System;
2using System.Collections.Generic;
3using System.Linq;
4
5IEnumerable<string> values = new[] { "Alpha", "Beta", "Gamma" };
6
7bool hasBeta = values.Any(v =>
8    string.Equals(v, "beta", StringComparison.OrdinalIgnoreCase));
9
10Console.WriteLine(hasBeta);

This is easy to read and keeps the comparison rule visible at the call site.

Why the Default Contains Is Not Enough

IEnumerable<string>.Contains("beta") uses the default equality comparer unless you provide one. That default comparison is case-sensitive for strings.

If you want case-insensitive behavior, either:

  • pass an explicit comparer where the overload supports it, or
  • use Any with string.Equals, or
  • move the data into a comparer-aware collection.

For example:

csharp
1using System;
2using System.Linq;
3
4string[] values = { "Alpha", "Beta", "Gamma" };
5bool hasBeta = values.Contains("beta", StringComparer.OrdinalIgnoreCase);
6Console.WriteLine(hasBeta);

That overload is clean when it is available and the collection is already in memory.

Use a HashSet for Repeated Lookups

If the code performs membership checks many times, repeated linear scans are wasteful. Convert the data once into a HashSet<string> that uses the correct comparer.

csharp
1using System;
2using System.Collections.Generic;
3
4var allowed = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
5{
6    "Alpha",
7    "Beta",
8    "Gamma"
9};
10
11Console.WriteLine(allowed.Contains("BETA"));

This is usually the best choice for high-volume lookups such as command routing, feature-flag checks, or validation against an allowed list.

Choose the Right Comparison Semantics

For technical identifiers such as commands, role names, protocol keys, and configuration values, StringComparison.OrdinalIgnoreCase or StringComparer.OrdinalIgnoreCase is usually the right default. It is culture-independent and predictable.

For user-facing text, business rules may require culture-aware comparison instead.

csharp
bool found = values.Any(v =>
    string.Equals(v, "istanbul", StringComparison.CurrentCultureIgnoreCase));

That is a different policy from ordinal comparison. The point is not that one is always better, but that you should choose deliberately rather than accept accidental defaults.

Wrap the Rule in an Extension Method

If the same case-insensitive membership rule appears throughout the codebase, encapsulate it.

csharp
1using System;
2using System.Collections.Generic;
3using System.Linq;
4
5public static class StringEnumerableExtensions
6{
7    public static bool ContainsIgnoreCase(this IEnumerable<string> source, string target)
8    {
9        if (source == null) throw new ArgumentNullException(nameof(source));
10        if (target == null) return false;
11
12        return source.Any(s =>
13            string.Equals(s, target, StringComparison.OrdinalIgnoreCase));
14    }
15}

Usage stays clean:

csharp
var names = new[] { "Alice", "Bob" };
Console.WriteLine(names.ContainsIgnoreCase("BOB"));

A helper like this prevents different parts of the codebase from drifting into inconsistent comparison rules.

Test Null and Unicode Cases

Real systems usually need explicit decisions for edge cases:

  • should a null target return false or throw,
  • can the source contain null elements,
  • should accented characters be treated as equal,
  • should user-facing comparisons be culture-aware.

Those questions are more important than the raw syntax. Once you answer them, the correct comparer choice becomes much clearer.

Common Pitfalls

A common mistake is assuming default Contains on strings ignores case. It does not.

Another issue is repeatedly calling ToLower() or ToUpper() on every lookup. That works sometimes, but it is less clear than comparer-based APIs and can lead to inconsistent behavior across cultures.

Teams also often mix ordinal and culture-aware comparison rules in different layers of the same application. That is where subtle bugs begin.

Summary

  • For one-off checks, use Any with string.Equals and an explicit comparison mode.
  • If available, Contains with a StringComparer is also clean and direct.
  • For repeated lookups, prefer HashSet<string> with StringComparer.OrdinalIgnoreCase.
  • Use ordinal comparison for technical identifiers unless business rules require culture-aware text semantics.
  • Centralize comparison behavior so your application stays consistent.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.