NeuroAgent

How to Make Contains() Case Insensitive in C#

Learn multiple ways to make string.Contains() case insensitive in C# using StringComparison, extension methods, and best practices. Avoid i18n issues with efficient case-insensitive string comparison.

Question

How to make string.Contains() case insensitive in C#

Is there a way to make the following return true?

csharp
string title = "ASTRINGTOTEST";
title.Contains("string");

There doesn’t seem to be an overload that allows me to set the case sensitivity. Currently I UPPERCASE them both, but that’s just silly (by which I am referring to the i18n issues that come with up- and down casing).

NeuroAgent

The string.Contains() method in C# doesn’t directly support case-insensitive comparison, but you can achieve this using the StringComparison parameter by implementing an extension method or by converting both strings to the same case using culturally appropriate comparison methods. The recommended approach is to use StringComparison.OrdinalIgnoreCase which handles case-insensitive comparison more efficiently than converting both strings to uppercase or lowercase.

Contents


Basic Solution with StringComparison

The string.Contains() method doesn’t have a built-in overload for case-insensitive comparison, but you can work around this limitation by using the IndexOf() method which accepts a StringComparison parameter:

csharp
string title = "ASTRINGTOTEST";
bool contains = title.IndexOf("string", StringComparison.OrdinalIgnoreCase) >= 0;

This approach directly addresses your requirement by performing a case-insensitive search. The StringComparison.OrdinalIgnoreCase enum value specifies that the comparison should ignore case differences, making “ASTRINGTOTEST” contain “string” return true.

Why IndexOf() Instead of Contains()?

The string class provides several comparison methods with different overloads:

  • Contains(): Only accepts a string parameter
  • IndexOf(): Accepts both string and StringComparison parameters
  • StartsWith(): Has StringComparison overloads
  • EndsWith(): Has StringComparison overloads

This inconsistency in the .NET Framework is why developers often need to work around the limitation using IndexOf().


Extension Method Approach

For cleaner, more readable code, you can create an extension method that provides case-insensitive Contains() functionality:

csharp
public static class StringExtensions
{
    public static bool ContainsIgnoreCase(this string source, string value)
    {
        if (string.IsNullOrEmpty(source) || string.IsNullOrEmpty(value))
            return false;
            
        return source.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0;
    }
}

// Usage
string title = "ASTRINGTOTEST";
bool contains = title.ContainsIgnoreCase("string");

This extension method provides a more intuitive API that matches your original intent while maintaining proper case-insensitive comparison behavior.

Advanced Extension Method with Multiple Options

You can extend this further to support different comparison modes:

csharp
public static bool Contains(this string source, string value, StringComparison comparisonType)
{
    if (string.IsNullOrEmpty(source) || string.IsNullOrEmpty(value))
        return false;
        
    return source.IndexOf(value, comparisonType) >= 0;
}

// Usage
string title = "ASTRINGTOTEST";
bool containsOrdinal = title.Contains("string", StringComparison.Ordinal);
bool containsIgnoreCase = title.Contains("string", StringComparison.OrdinalIgnoreCase);
bool containsCurrentCulture = title.Contains("string", StringComparison.CurrentCultureIgnoreCase);

Performance Comparison

Different approaches have varying performance characteristics. Here’s a comparison of common methods:

Method Performance Notes Memory Usage Thread Safety
ToUpper().Contains() Creates new string objects Higher Safe
ToLower().Contains() Creates new string objects Higher Safe
IndexOf(StringComparison) Works with original strings Lower Safe
Extension Method Minimal overhead Lower Safe
csharp
// Performance test example
var sw = Stopwatch.StartNew();
for (int i = 0; i < 1000000; i++)
{
    bool result = title.IndexOf("string", StringComparison.OrdinalIgnoreCase) >= 0;
}
sw.Stop();
Console.WriteLine($"IndexOf with StringComparison: {sw.ElapsedMilliseconds}ms");

The IndexOf() with StringComparison approach is generally the most efficient because it:

  • Doesn’t allocate new string objects
  • Uses optimized native comparison routines
  • Works directly with the original string data

Internationalization Considerations

Your concern about i18n issues with case conversion is valid. Different cultures handle case conversion differently, and simple ToUpper() or ToLower() can lead to incorrect results in international applications.

Problems with Simple Case Conversion

csharp
// Problematic examples
string german = "STRASSE"; // German word for "street"
string turkish = "I";      // Turkish dotted I

// These can fail in different cultures
bool germanToUpper = german.ToUpper() == "STRASSE".ToUpper(); // Works
bool turkishIssue = turkish.ToUpper() == "i".ToUpper();       // Problematic

Culturally Sensitive Approaches

csharp
// Using CurrentCulture (appropriate for user-facing text)
bool containsCurrent = title.Contains("string", StringComparison.CurrentCultureIgnoreCase);

// Using InvariantCulture (appropriate for data processing)
bool containsInvariant = title.Contains("string", StringComparison.InvariantCultureIgnoreCase);

// Using Ordinal (best for performance when case is the only concern)
bool containsOrdinal = title.Contains("string", StringComparison.OrdinalIgnoreCase);

When to Use Each StringComparison

  • StringComparison.Ordinal: Best performance, suitable for internal identifiers, file paths, and data processing
  • StringComparison.OrdinalIgnoreCase: Good balance of performance and case-insensitive matching
  • StringComparison.CurrentCulture: Appropriate for user-facing text and display purposes
  • StringComparison.InvariantCulture: Suitable for persistent data and cross-culture scenarios

Best Practices

1. Choose the Right StringComparison

csharp
// For internal data processing
bool contains = data.Contains("search", StringComparison.OrdinalIgnoreCase);

// For user-facing text
bool displayContains = userInput.Contains("search", StringComparison.CurrentCultureIgnoreCase);

// For file system operations
bool fileExists = fileName.Contains("temp", StringComparison.Ordinal);

2. Consider Performance in Loops

csharp
// Cache StringComparison for repeated operations
var comparison = StringComparison.OrdinalIgnoreCase;
foreach (var item in items)
{
    if (item.Value.Contains("search", comparison))
    {
        // Process match
    }
}

3. Handle Null and Empty Strings Safely

csharp
public static bool SafeContainsIgnoreCase(this string source, string value)
{
    if (source == null || value == null)
        return false;
        
    if (source.Length == 0 || value.Length == 0)
        return source.Length == value.Length;
        
    return source.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0;
}

4. Document Your Comparison Strategy

csharp
/// <summary>
/// Searches for text in a case-insensitive manner using ordinal comparison.
/// Suitable for internal data processing and performance-critical scenarios.
/// </summary>
public bool ContainsSearchTerm(string searchTerm)
{
    return Content.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase) >= 0;
}

Advanced Scenarios

Regular Expressions with Case Insensitivity

For complex pattern matching, regular expressions provide powerful case-insensitive options:

csharp
using System.Text.RegularExpressions;

string title = "ASTRINGTOTEST";
bool contains = Regex.IsMatch(title, "string", RegexOptions.IgnoreCase);

Custom Comparison with IEqualityComparer

csharp
public class CaseInsensitiveComparer : IEqualityComparer<string>
{
    public bool Equals(string x, string y)
    {
        return string.Equals(x, y, StringComparison.OrdinalIgnoreCase);
    }

    public int GetHashCode(string obj)
    {
        return obj?.ToLower().GetHashCode() ?? 0;
    }
}

// Usage
var comparer = new CaseInsensitiveComparer();
bool contains = new List<string> { "ASTRINGTOTEST" }.Contains("string", comparer);

Multiple String Search Methods

csharp
public static class StringSearch
{
    public static bool ContainsAny(this string source, IEnumerable<string> values, StringComparison comparison = StringComparison.OrdinalIgnoreCase)
    {
        return values.Any(value => source.Contains(value, comparison));
    }

    public static bool ContainsAll(this string source, IEnumerable<string> values, StringComparison comparison = StringComparison.OrdinalIgnoreCase)
    {
        return values.All(value => source.Contains(value, comparison));
    }
}

// Usage
string text = "The quick brown fox";
bool hasAny = text.ContainsAny(new[] { "QUICK", "FOX" });
bool hasAll = text.ContainsAll(new[] { "QUICK", "BROWN" });

For complete .NET documentation on string comparison methods, refer to the official Microsoft .NET documentation.

Conclusion

  • Use IndexOf() with StringComparison.OrdinalIgnoreCase as the primary solution for case-insensitive string contains operations in C#
  • Create extension methods for cleaner, more readable code that matches your original intent
  • Consider performance implications - the IndexOf approach is more efficient than converting both strings to uppercase or lowercase
  • Be mindful of internationalization - use appropriate StringComparison values based on your use case (Ordinal for performance, CurrentCulture for user-facing text)
  • Implement null-safe methods to handle edge cases in production code
  • Choose the right comparison strategy based on whether you’re dealing with internal data processing, user-facing text, or persistent data storage

The case-insensitive Contains() functionality can be easily implemented once you understand the available StringComparison options and their appropriate use cases in your application.