String Searching - Finding and Matching Text in C#

Vaibhav • September 10, 2025

Searching within strings is a core programming skill-whether you’re looking for a keyword, checking if a filename ends with .txt, or extracting a value from user input. C# provides a rich set of methods for searching, matching, and extracting text: Contains, IndexOf, StartsWith, EndsWith, and more. This article covers the most practical patterns for string searching, including case and culture options, substring extraction, and common pitfalls.

This article builds on string comparison and formatting. We’ll focus on safe, robust searching patterns-no regular expressions yet (that’s later in the chapter).

1) Contains - does a substring exist?

Use Contains to check if a string contains another string. In modern C#, always use the overload that takes a StringComparison for explicit case/culture control.

string text = "C# String Searching";
bool found = text.Contains("string", StringComparison.OrdinalIgnoreCase); // True
bool notFound = text.Contains("python", StringComparison.OrdinalIgnoreCase); // False
  • Default Contains is case-sensitive and culture-sensitive (not recommended for most cases).
  • Always specify StringComparison for clarity and safety.

2) IndexOf - where does a substring appear?

IndexOf returns the zero-based index of the first occurrence of a substring, or -1 if not found. It’s the workhorse for locating, extracting, and splitting text.

string sentence = "Find the position of 'the' in this sentence.";
int pos = sentence.IndexOf("the", StringComparison.OrdinalIgnoreCase); // 5
if (pos >= 0)
    Console.WriteLine($"'the' found at index {pos}");
  • Returns -1 if not found-always check before using the result.
  • Supports StringComparison for case/culture control.
  • Can search from a specific start index: IndexOf(value, startIndex, ...)

3) StartsWith and EndsWith - prefix and suffix checks

Use these methods to check if a string begins or ends with a specific substring. Always use the overload with StringComparison.

string filename = "report.PDF";
bool isPdf = filename.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase); // True

string command = "exit";
bool isExit = command.StartsWith("ex", StringComparison.OrdinalIgnoreCase); // True
  • Essential for file extensions, protocol prefixes, and command parsing.
  • Case/culture options make your code robust across platforms and user input.

4) Extracting substrings - Substring and IndexOf

Combine IndexOf and Substring to extract parts of a string.

string email = "[email protected]";
int at = email.IndexOf("@");
if (at >= 0)
{
    string user = email.Substring(0, at); // "user"
    string domain = email.Substring(at + 1); // "example.com"
    Console.WriteLine($"User: {user}, Domain: {domain}");
}
  • Always check for -1 before calling Substring to avoid exceptions.
  • Use Substring(start, length) for more control.

5) Finding all occurrences - looping with IndexOf

To find every occurrence of a substring, loop with IndexOf and update the start index.

string text = "one fish two fish red fish blue fish";
string word = "fish";
int count = 0, pos = 0;
while ((pos = text.IndexOf(word, pos, StringComparison.OrdinalIgnoreCase)) != -1)
{
    count++;
    pos += word.Length;
}
Console.WriteLine($"'fish' appears {count} times.");
  • Advance the index by the length of the substring to avoid infinite loops.
  • Works for overlapping and non-overlapping matches.

6) Case and culture: always specify your intent

All searching methods have overloads for StringComparison. Use OrdinalIgnoreCase for protocol/data, CurrentCultureIgnoreCase for user-facing text.

string input = "Straße";
bool found = input.Contains("strasse", StringComparison.CurrentCultureIgnoreCase); // True in German culture
  • Culture-aware searching can treat some characters as equivalent (e.g., ß and ss in German).
  • Be explicit for predictable, portable code.

7) Defensive searching - nulls, empty strings, and boundaries

  • Always check for null or empty strings before searching.
  • Handle -1 results from IndexOf and friends.
  • Don’t assume a substring exists-always check before extracting.
string s = null;
if (!string.IsNullOrEmpty(s) && s.Contains("test", StringComparison.OrdinalIgnoreCase))
    Console.WriteLine("Found!");

8) Practical mini-project: parsing a simple key-value pair

string line = "user=vaibhav;score=100;level=5";
string[] pairs = line.Split(';');
foreach (string pair in pairs)
{
    int eq = pair.IndexOf('=');
    if (eq > 0)
    {
        string key = pair.Substring(0, eq);
        string value = pair.Substring(eq + 1);
        Console.WriteLine($"{key}: {value}");
    }
}
  • Splits the string into key-value pairs, then extracts and prints each one.
  • Robust to missing or extra spaces, as long as the format is consistent.

9) Checklist - searching habits for robust code

  • Always use StringComparison overloads for searching methods.
  • Check for -1 before extracting substrings.
  • Use string.Join and string.Split for lists and CSV-style data.
  • Loop with IndexOf to find all matches.
  • Handle null and empty strings defensively.
  • Document your case/culture assumptions for future maintainers.

Summary

String searching is about more than just “does it contain?”-it’s about finding, matching, and extracting text safely and predictably. Use Contains, IndexOf, StartsWith, and EndsWith with explicit StringComparison options. Always check for -1 before extracting, and handle null or empty strings gracefully. With these patterns, your code will be robust, readable, and ready for real-world text processing.