Skip to main content

String Predicates and Matching

These methods answer yes/no questions about a string: does it contain, start with or end with one of several values, is it blank, is it Base64, does it match a *.csv pattern. A few also measure a string: how often a value occurs, and what two strings share at the start or end. Reach for them when you would otherwise chain || calls or guard against null before every check.

All methods live in the System namespace, so you do not need a using directive.

At a glance​

MethodWhat it does
Contains(params string[])true if the string contains any of the values (ordinal, case-sensitive).
ContainsAnytrue if the string contains any of the values, using the StringComparison you pass.
ContainsIgnoreCasetrue if the string contains the value, or any of the values, ignoring case.
StartsWith(params string[])true if the string starts with any of the prefixes (ordinal, case-sensitive).
StartsWithIgnoreCasetrue if the string starts with the prefix, or any of the prefixes, ignoring case.
EndsWith(params string[])true if the string ends with any of the suffixes (ordinal, case-sensitive).
EndsWithIgnoreCasetrue if the string ends with the suffix, or any of the suffixes, ignoring case.
EqualsIgnoreCaseCompares two strings ignoring case. Two null strings are equal.
IsNullOrEmptyExtension form of string.IsNullOrEmpty, with nullable flow analysis.
IsNullOrWhiteSpaceExtension form of string.IsNullOrWhiteSpace, with nullable flow analysis.
IsBase64true if the string is valid standard Base64 with no whitespace.
MatchesWildcardMatches the whole string against a glob pattern with * and ?.
CountOccurrencesCounts non-overlapping occurrences of a string, or occurrences of a character.
CommonPrefixReturns the longest start that two strings share.
CommonSuffixReturns the longest end that two strings share.
IsAnagramOftrue if two strings use the same letters and digits, ignoring case, spaces and punctuation.

The null-receiver rule​

The string you call a method on (the receiver) may be null. The values you pass in may not.

  • A null receiver gives a "no" answer: predicates return false, CountOccurrences returns 0. No NullReferenceException.
  • A null argument (the value to look for, the pattern, the other string) throws ArgumentNullException with the parameter name. Arguments are checked first, so this happens even when the receiver is also null.
  • Every comparison is ordinal unless you pass a StringComparison. Nothing depends on the current culture.
Null receiver versus null argument
string? couponCode = null;

bool hasPrefix = couponCode.StartsWithIgnoreCase("SPRING"); // → false
int dashes = couponCode.CountOccurrences('-'); // → 0
bool blank = couponCode.IsNullOrWhiteSpace(); // → true

string? prefix = null;
couponCode.StartsWithIgnoreCase(prefix!); // throws ArgumentNullException (ParamName "toCheck")
MethodResult for a null receiver
Contains, ContainsAny, ContainsIgnoreCasefalse
StartsWith, StartsWithIgnoreCase, EndsWith, EndsWithIgnoreCasefalse
EqualsIgnoreCasetrue if the other string is also null, otherwise false
IsNullOrEmpty, IsNullOrWhiteSpacetrue
IsBase64, MatchesWildcard, IsAnagramOffalse
CountOccurrences0
CommonPrefix, CommonSuffixthrows ArgumentNullException (these return a string, so there is no safe answer)

See Conventions for the rules that apply across the whole library.

Contains, StartsWith and EndsWith​

Each family has a case-sensitive form that takes several values and an IgnoreCase form that takes one or several values. They return true as soon as one value matches. An empty list of values returns false.

One argument calls the BCL method

string already has instance methods Contains(string), StartsWith(string) and EndsWith(string). C# prefers instance methods, so a call with one string argument, such as url.StartsWith("https://"), runs the BCL method, not this library. That method throws NullReferenceException on a null string, and the BCL StartsWith(string) / EndsWith(string) compare with the current culture.

The extensions take over when you pass two or more values, or a string[]. For a single value, use the IgnoreCase method, ContainsAny(comparison, value), or pass a one-element array.

Contains​

Contains(params string[] toCheck) checks whether the string contains any of the values. The comparison is ordinal and case-sensitive.

Flag log lines that report a problem
string logLine = "2024-05-01 12:00:03 ERROR Payment gateway timeout";

bool isProblem = logLine.Contains("ERROR", "FATAL"); // → true
bool lowerCase = logLine.Contains("error", "fatal"); // → false

string[] blockedWords = { "casino", "lottery" };
string comment = "Win the lottery today!";
bool isSpam = comment.Contains(blockedWords); // → true
  • An empty string is contained in every string, so "abc".Contains("x", "") returns true.
  • A null array or a null item in it throws ArgumentNullException (ParamName is "toCheck").

ContainsAny​

ContainsAny(StringComparison comparison, params string[] toCheck) is the same check with a comparison you choose. Use it when you need case-insensitive matching against a list built at run time, or a culture-aware comparison.

Search product names
string productName = "Wireless Mouse M185";

bool isPeripheral = productName.ContainsAny(StringComparison.OrdinalIgnoreCase, "mouse", "keyboard"); // → true
bool exactCase = productName.ContainsAny(StringComparison.Ordinal, "mouse", "keyboard"); // → false

The comparison is passed straight to string.IndexOf, so CurrentCulture and InvariantCultureIgnoreCase work too.

ContainsIgnoreCase​

ContainsIgnoreCase(string toCheck) and ContainsIgnoreCase(params string[] toCheck) check for one value, or any of several values, using StringComparison.OrdinalIgnoreCase.

Route support e-mails by subject
string subject = "Re: Invoice INV-2041 is OVERDUE";

bool aboutInvoice = subject.ContainsIgnoreCase("invoice"); // → true
bool escalate = subject.ContainsIgnoreCase("urgent", "overdue"); // → true
bool aboutRefund = subject.ContainsIgnoreCase("refund"); // → false

string? emptySubject = null;
bool fromNull = emptySubject.ContainsIgnoreCase("invoice"); // → false
Obsolete overloads

ContainsIgnoreCase(StringComparison comp, string toCheck) and ContainsIgnoreCase(StringComparison comp, params string[] toCheck) still compile but are marked [Obsolete]: the comparison argument decides the case rule, so the name "IgnoreCase" is misleading. Replace them with ContainsAny.

Migrating from the obsolete overload
string customerNote = "Gold member since 2019";

// Before: compiler warning CS0618
bool oldStyle = customerNote.ContainsIgnoreCase(StringComparison.Ordinal, "VIP", "Gold"); // → true

// After
bool newStyle = customerNote.ContainsAny(StringComparison.Ordinal, "VIP", "Gold"); // → true

StartsWith​

StartsWith(params string[] toCheck) checks whether the string starts with any of the prefixes. Ordinal and case-sensitive.

Accept only web URLs
string callbackUrl = "https://shop.example.com/orders/1042";

bool isWebUrl = callbackUrl.StartsWith("http://", "https://"); // → true
bool isFtp = callbackUrl.StartsWith("ftp://", "sftp://"); // → false
bool upperCase = callbackUrl.StartsWith("HTTP://", "HTTPS://"); // → false

StartsWithIgnoreCase​

StartsWithIgnoreCase(string toCheck) and StartsWithIgnoreCase(params string[] toCheck) check one prefix, or any of several, with StringComparison.OrdinalIgnoreCase.

Read the authorization scheme
string authorization = "bearer eyJhbGciOiJIUzI1NiJ9";

bool isBearer = authorization.StartsWithIgnoreCase("Bearer "); // → true
bool isSupported = authorization.StartsWithIgnoreCase("Basic ", "Bearer "); // → true
bool isDigest = authorization.StartsWithIgnoreCase("Digest "); // → false

EndsWith​

EndsWith(params string[] toCheck) checks whether the string ends with any of the suffixes. Ordinal and case-sensitive.

Detect report formats
string exportFile = "sales-2024-q3.csv";

bool isTabular = exportFile.EndsWith(".csv", ".xlsx"); // → true
bool isImage = exportFile.EndsWith(".png", ".jpg"); // → false

EndsWithIgnoreCase​

EndsWithIgnoreCase(string toCheck) and EndsWithIgnoreCase(params string[] toCheck) check one suffix, or any of several, with StringComparison.OrdinalIgnoreCase. File extensions are the usual case.

Validate an uploaded receipt
string upload = "receipt-2041.JPG";

bool exactCase = upload.EndsWith(".jpg", ".png"); // → false
bool isImage = upload.EndsWithIgnoreCase(".jpg", ".png"); // → true
bool isPdf = upload.EndsWithIgnoreCase(".pdf"); // → false

Equality and blank checks​

EqualsIgnoreCase​

EqualsIgnoreCase(string? toCheck) compares two strings with StringComparison.OrdinalIgnoreCase. Both sides may be null; two null strings count as equal.

Compare e-mail addresses
string? enteredEmail = "Ana.Lopez@Example.com";
string storedEmail = "ana.lopez@example.com";

bool sameUser = enteredEmail.EqualsIgnoreCase(storedEmail); // → true

string? missing = null;
bool bothMissing = missing.EqualsIgnoreCase(null); // → true
bool oneMissing = missing.EqualsIgnoreCase("ana"); // → false
Changed in 2.0

In 1.x, EqualsIgnoreCase returned false for two null strings. It now returns true, the same as string.Equals. See Upgrading from 1.x.

IsNullOrEmpty​

IsNullOrEmpty() is string.IsNullOrEmpty as an extension, so it reads left to right and works on a null receiver. The receiver carries [NotNullWhen(false)]: after a false result the compiler knows the string is not null.

Values
string? orderNote = "";

bool noNote = orderNote.IsNullOrEmpty(); // → true
bool space = " ".IsNullOrEmpty(); // → false
bool text = "a".IsNullOrEmpty(); // → false

IsNullOrWhiteSpace​

IsNullOrWhiteSpace() is string.IsNullOrWhiteSpace as an extension, also with [NotNullWhen(false)]. Use it to treat blank form input as missing.

Nullable flow analysis
static string DisplayName(string? nickname, string fullName)
{
if (!nickname.IsNullOrWhiteSpace())
{
// No CS8602 warning: the compiler knows nickname is not null here.
return nickname.Trim();
}

return fullName;
}

string blankNick = DisplayName(" \t\n", "Ana Lopez"); // → "Ana Lopez"
string realNick = DisplayName(" Ana ", "Ana Lopez"); // → "Ana"

Format checks​

IsBase64​

IsBase64() returns true when the string decodes as standard Base64. It is stricter than Convert.FromBase64String: the length must be a multiple of 4 and the string may not contain any whitespace.

Check a payload before decoding it
bool padded = "SGVsbG8=".IsBase64(); // → true
bool full = "SGVsbG8h".IsBase64(); // → true
bool tooShort = "SGVsbG8".IsBase64(); // → false (length is not a multiple of 4)
bool spaced = "SGVs bG8".IsBase64(); // → false (contains whitespace)
bool symbols = "!!!!".IsBase64(); // → false
bool empty = "".IsBase64(); // → false
note

Only the standard alphabet (A–Z, a–z, 0–9, +, /, with = padding) is accepted. URL-safe Base64, which uses - and _, returns false. The check decodes into a temporary buffer, so it allocates about three quarters of the input length.

For encoding and decoding helpers, see Encoding and validation.

Pattern matching and counting​

MatchesWildcard​

MatchesWildcard(string pattern) and MatchesWildcard(string pattern, bool ignoreCase) test the whole string against a glob pattern. * matches any run of characters, including none. ? matches exactly one character. Every other character matches itself. Without ignoreCase the match is ordinal and case-sensitive.

Match export file names
bool monthly = "report-2024.csv".MatchesWildcard("report-*.csv"); // → true
bool noDash = "report.csv".MatchesWildcard("report-*.csv"); // → false
bool emptyRun = "report-.csv".MatchesWildcard("report-*.csv"); // → true
bool oneChar = "abc".MatchesWildcard("a?c"); // → true
bool noChar = "ac".MatchesWildcard("a?c"); // → false

bool exactCase = "Report.CSV".MatchesWildcard("*.csv"); // → false
bool anyCase = "Report.CSV".MatchesWildcard("*.csv", ignoreCase: true); // → true
Filter a list of uploads
string[] uploads = { "orders.csv", "logo.png", "Refunds.CSV", "notes.txt" };

List<string> csvFiles = uploads
.Where(name => name.MatchesWildcard("*.csv", ignoreCase: true))
.ToList(); // → ["orders.csv", "Refunds.CSV"]
  • The pattern must cover the entire string. "a".MatchesWildcard("") is false; "".MatchesWildcard("*") and "".MatchesWildcard("") are true; "".MatchesWildcard("?") is false.
  • There is no escape character and no [abc] character class. A * or ? in the pattern is always a wildcard.
  • ignoreCase: true compares characters with char.ToUpperInvariant, so it does not depend on the current culture.
  • ? matches one UTF-16 char. A character outside the Basic Multilingual Plane, such as most emoji, is two chars.
  • A null text returns false; a null pattern throws ArgumentNullException (ParamName is "pattern").
Safe for user-supplied patterns

The matcher is iterative, with no regex and no recursion. A hostile pattern such as fifty * followed by b, run against 10,000 a characters, finishes quickly and returns false. You can accept patterns from users without adding a timeout.

CountOccurrences​

CountOccurrences has three overloads:

  • CountOccurrences(string value) counts non-overlapping occurrences, ordinal and case-sensitive.
  • CountOccurrences(string value, StringComparison comparison) does the same with the comparison you choose.
  • CountOccurrences(char value) counts a single character.

Matches do not overlap: after a match, the search continues after the end of that match.

Counting
int ana = "banana".CountOccurrences("ana"); // → 1
int letterA = "banana".CountOccurrences("a"); // → 3
int pairs = "aaaa".CountOccurrences("aa"); // → 2
int anyCase = "Aa aA".CountOccurrences("aa", StringComparison.OrdinalIgnoreCase); // → 2
int commas = "a,b,c".CountOccurrences(','); // → 2
Count the columns of a CSV line
string csvLine = "1042,Ana Lopez,2024-05-01,149.90";

int columns = csvLine.CountOccurrences(',') + 1; // → 4
  • A null text returns 0 for every overload.
  • A null value throws ArgumentNullException; an empty value throws ArgumentException. Both report ParamName "value". The value is checked before the text, so an empty value throws even when the text is null.

Comparing two strings​

CommonPrefix​

CommonPrefix(string other) returns the longest start that both strings share, compared ordinally (case-sensitive). The result may be empty.

Find the shared part of two routes
string firstRoute = "/api/orders/1042";
string secondRoute = "/api/orders/2077";

string sharedRoute = firstRoute.CommonPrefix(secondRoute); // → "/api/orders/"
string word = "interstellar".CommonPrefix("internet"); // → "inter"
string shorter = "ab".CommonPrefix("abc"); // → "ab"
string none = "abc".CommonPrefix("xyz"); // → ""
string caseMatters = "Order".CommonPrefix("order"); // → ""

CommonSuffix​

CommonSuffix(string other) returns the longest end that both strings share, compared ordinally (case-sensitive). The result may be empty.

Find the shared ending of two documents
string invoiceFile = "invoice-2041.pdf";
string receiptFile = "receipt-2041.pdf";

string sharedEnd = invoiceFile.CommonSuffix(receiptFile); // → "-2041.pdf"
string word = "walking".CommonSuffix("talking"); // → "alking"
string none = "abc".CommonSuffix("xyz"); // → ""
Not null-safe

Unlike the predicates on this page, CommonPrefix and CommonSuffix take a non-nullable receiver. A null receiver or a null other throws ArgumentNullException (ParamName is "text" or "other").

IsAnagramOf​

IsAnagramOf(string other) returns true when both strings contain the same letters and digits, in any order. It ignores case, whitespace and punctuation. Letters are lower-cased with the invariant culture.

Anagrams
bool room = "Dormitory".IsAnagramOf("dirty room"); // → true
bool listen = "listen".IsAnagramOf("silent"); // → true
bool gentleman = "A gentleman".IsAnagramOf("Elegant man!"); // → true
bool different = "abc".IsAnagramOf("abd"); // → false
bool extraLetter = "abc".IsAnagramOf("abcc"); // → false
  • Two strings without any letter or digit are anagrams: "!!!".IsAnagramOf("???") and "".IsAnagramOf("") are both true.
  • A null receiver returns false; a null other throws ArgumentNullException (ParamName is "other").

For similarity scores and edit distances rather than exact matches, see Fuzzy and natural matching.

See also​