How to make string.Contains() case insensitive in C#
Is there a way to make the following return true?
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).
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
- Extension Method Approach
- Performance Comparison
- Internationalization Considerations
- Best Practices
- Advanced Scenarios
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:
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:
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:
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 |
// 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
// 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
// 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
// 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
// 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
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
/// <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:
using System.Text.RegularExpressions;
string title = "ASTRINGTOTEST";
bool contains = Regex.IsMatch(title, "string", RegexOptions.IgnoreCase);
Custom Comparison with IEqualityComparer
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
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.