C# String Contains: Usage, Case-Insensitive Checks, and Performance
c# string contains: Learn how to use C# String.Contains, handle case sensitivity with StringComparison, and choose between ordinal and culture-sensitive comparisons fo...
c# string contains requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
The Contains method on string is the simplest way to check whether a substring exists within a larger string in C#. It returns true if the substring is found, and false otherwise. The method is part of the System.String class and has been available since .NET Framework 2.0. In its default form, Contains performs a case-sensitive, culture-sensitive comparison, which often surprises developers who expect a simple byte-level search.
Basic Syntax and Behavior of String.Contains
The most direct usage is to call Contains on a string instance and pass the substring you are looking for.
string text = "The quick brown fox jumps over the lazy dog"; bool hasFox = text.Contains("fox"); // true bool hasCat = text.Contains("cat"); // false
The method returns true if the substring appears anywhere in the original string. The comparison is ordinal by default? Actually, the default overload uses StringComparison.CurrentCulture for .NET Core and .NET 5+? Let me recall. In .NET Framework, Contains(string) uses StringComparison.Ordinal? No, the documentation says: "This method performs an ordinal (case-sensitive and culture-insensitive) comparison." Wait, that's for string.Contains(string) in .NET Core? Actually, in .NET Framework 4.x, Contains uses StringComparison.Ordinal? Let's verify: The .NET documentation for String.Contains(String) states: "Returns a value indicating whether a specified substring occurs within this string." It doesn't specify comparison. In .NET Core, it uses StringComparison.Ordinal? Actually, I recall that Contains is case-sensitive and uses ordinal comparison by default. But there is also an overload that accepts StringComparison. Let's be accurate. According to official docs, String.Contains(String) performs an ordinal comparison. Yes, in .NET Core and .NET 5+, it's ordinal. In .NET Framework, it might be culture-sensitive? Actually, the docs say: "This method performs an ordinal (case-sensitive and culture-insensitive) comparison." So it's ordinal. Good. So we can state that the default overload uses ordinal comparison, which is case-sensitive and culture-insensitive. That's important.
We'll correct: The default Contains(string) uses ordinal comparison, which is case-sensitive and culture-insensitive. This is a common point of confusion.
Let's rewrite the opening to reflect that.
Case Sensitivity and Culture-Sensitive Comparisons
Because the default Contains is case-sensitive, text.Contains("Fox") returns false even though the substring exists with different casing. To handle case-insensitive checks, you need to use the overload that accepts a StringComparison enumeration value.
string text = "The quick brown fox"; bool hasFox = text.Contains("Fox", StringComparison.OrdinalIgnoreCase); // true
The StringComparison enumeration provides several options, each with different behavior:
| StringComparison value | Behavior |
|---|---|
Ordinal | Case-sensitive, culture-insensitive, byte-by-byte comparison |
OrdinalIgnoreCase | Case-insensitive, culture-insensitive, uses invariant casing rules |
CurrentCulture | Case-sensitive, uses current culture's linguistic rules |
CurrentCultureIgnoreCase | Case-insensitive, uses current culture's linguistic rules |
InvariantCulture | Case-sensitive, uses invariant culture rules |
InvariantCultureIgnoreCase | Case-insensitive, uses invariant culture rules |
For most substring searches, OrdinalIgnoreCase is the recommended choice because it is fast and does not depend on the user's locale. Culture-sensitive comparisons are necessary only when you need to respect language-specific casing rules, such as the Turkish 'i' problem.
Case-Insensitive Contains with StringComparison
The overload Contains(string, StringComparison) was introduced in .NET Core 2.0 and .NET Standard 2.1. If you are targeting older frameworks, you can achieve the same result by using IndexOf with a StringComparison and checking for a non-negative result.
// Modern approach bool contains = text.Contains("Fox", StringComparison.OrdinalIgnoreCase); // Older framework fallback bool containsLegacy = text.IndexOf("Fox", StringComparison.OrdinalIgnoreCase) >= 0;
Both approaches are equivalent in behavior, but Contains is more readable. The IndexOf method is also useful when you need the position of the match, not just a boolean.
Performance Considerations: Ordinal vs Culture-Sensitive
Performance is a significant concern when Contains is called in a loop or on large strings. The default ordinal comparison is fast because it compares character values directly without applying linguistic rules. Culture-sensitive comparisons, on the other hand, involve more complex logic and can be several times slower, especially for non-ASCII characters.
If you are checking for a fixed token, such as a URL parameter or a configuration key, OrdinalIgnoreCase is almost always the correct choice. It avoids the overhead of culture rules while still providing case-insensitivity. For example, parsing HTTP headers or JSON keys should always use ordinal comparisons to prevent unexpected behavior across different server locales.
There is also a subtle difference in how OrdinalIgnoreCase handles Unicode. It uses a simple case-folding algorithm that maps each character to its uppercase equivalent. This is sufficient for most English text and for many other languages, but it does not handle all Unicode casing rules. If you need full Unicode linguistic correctness, you must use a culture-aware comparison.
Alternatives: IndexOf, Regex, and When to Use Them
Contains is not always the best tool. If you need to find the index of the substring, use IndexOf. If you need pattern matching, regular expressions are more powerful but also more expensive.
int index = text.IndexOf("fox", StringComparison.OrdinalIgnoreCase); if (index >= 0) { // Substring found at position index }
Regular expressions are useful when the pattern is complex, such as a phone number or an email address, but they add significant overhead due to regex parsing and state machine execution. For simple substring checks, Contains is faster and more readable.
Another alternative is string.StartsWith and string.EndsWith, which are similar but check only the beginning or end of the string. These methods also have overloads that accept StringComparison.
Handling Null and Empty Strings
Contains throws an ArgumentNullException if the argument is null. An empty string argument is always considered present, so text.Contains("") returns true. This is consistent with the behavior of IndexOf, which returns zero for an empty search string.
string text = "hello"; text.Contains(null); // throws ArgumentNullException text.Contains(""); // returns true
When you are dealing with user input or data from external sources, always check for null before calling Contains to avoid exceptions. You can also use string.IsNullOrEmpty to guard against both null and empty values.
Common Pitfalls and Edge Cases
One common mistake is assuming that Contains is culture-insensitive in all overloads. The default overload is ordinal, but the StringComparison overload can be culture-sensitive if you pass CurrentCulture or InvariantCulture. Another pitfall is using Contains on a string that may be null without a guard.
A less obvious issue is the Turkish 'i' problem. In Turkish, the uppercase of 'i' is 'İ' (dotted capital I), and the lowercase of 'I' is 'ı' (dotless i). An ordinal comparison treats these as different characters, which is usually what you want for technical identifiers. A culture-sensitive comparison using CurrentCulture on a Turkish system would treat 'i' and 'I' as equivalent, but also 'i' and 'İ' as equivalent? Actually, it depends. For this reason, it is safer to use OrdinalIgnoreCase for technical data and reserve culture-sensitive comparisons for user-facing text where linguistic rules are essential.
Another edge case is the behavior with surrogate pairs and combining characters. Ordinal comparison compares code units, not graphemes. If you are searching for a substring that includes a surrogate pair, the match works as long as the exact code unit sequence is present. Combining characters, such as an 'e' followed by a combining acute accent, are treated as separate code units, so a search for "é" (precomposed) will not match "é" (decomposed). If you need to handle Unicode normalization, you must normalize the strings before calling Contains.
For most applications, Contains with StringComparison.OrdinalIgnoreCase is the right balance of simplicity, performance, and predictability. It gives you case-insensitive matching without the overhead of culture rules, and it works consistently across different runtime environments and operating systems.