C#idiomatic C#
searchcsharp/fundamentals/strings/common-tasks

Language

Search strings in C#

Learn how to find text within strings in C# with the Contains, StartsWith, EndsWith, IndexOf, and LastIndexOf methods, and how to choose the right StringComparison.

  • page csharp/fundamentals/strings/common-tasks/search
  • updated 05/21/2026

The System.String class includes methods that answer two everyday questions:

More complex search and replacement algorithms can be built using regular expressions. For more information on regular expressions and other string operations, see the language reference article on String operations.

Check whether a string contains text

Use Contains, StartsWith, or EndsWith to test for the presence of a substring:

sample · csharpdocs/csharp/fundamentals/strings/common-tasks/snippets/search/Program.cscontains

csharp
namespace SearchStrings;

public static class Program
{
    public static void Main()
    {
        Contains();
        Console.WriteLine();
        ContainsChar();
        Console.WriteLine();
        IndexOfExample();
    }

    private static void Contains()
    {
        // <contains>
        string factMessage = "Extension methods have all the capabilities of regular static methods.";

        // Write the string and include the quotation marks.
        Console.WriteLine($"\"{factMessage}\"");

        // Default comparisons are case sensitive.
        bool containsSearchResult = factMessage.Contains("extension");
        Console.WriteLine($"""Contains "extension"? {containsSearchResult}""");

        // For user-facing searches, pass a StringComparison value to control case and culture.
        bool ignoreCaseSearchResult = factMessage.StartsWith("extension", StringComparison.CurrentCultureIgnoreCase);
        Console.WriteLine($"""Starts with "extension"? {ignoreCaseSearchResult} (ignoring case)""");

        bool endsWithSearchResult = factMessage.EndsWith(".", StringComparison.Ordinal);
        Console.WriteLine($"Ends with '.'? {endsWithSearchResult}");
        // => "Extension methods have all the capabilities of regular static methods."
        // => Contains "extension"? False
        // => Starts with "extension"? True (ignoring case)
        // => Ends with '.'? True
        // </contains>
    }

    private static void ContainsChar()
    {
        // <ContainsChar>
        string path = "/usr/local/bin";
        bool hasSlash = path.Contains('/');
        Console.WriteLine($"Path contains '/': {hasSlash}");
        // => Path contains '/': True
        // </ContainsChar>
    }

    private static void IndexOfExample()
    {
        // <IndexOf>
        string factMessage = "Extension methods have all the capabilities of regular static methods.";

        Console.WriteLine($"\"{factMessage}\"");

        // Extract the text between the first and last occurrence of "methods".
        int first = factMessage.IndexOf("methods") + "methods".Length;
        int last = factMessage.LastIndexOf("methods");
        string between = factMessage.Substring(first, last - first);
        Console.WriteLine($"""Substring between "methods" and "methods": '{between}'""");
        // => "Extension methods have all the capabilities of regular static methods."
        // => Substring between "methods" and "methods": ' have all the capabilities of regular static '
        // </IndexOf>
    }
}

These methods default to case-sensitive, ordinal comparison. To accept user input or to ignore case for display text, pass a System.StringComparison value such as System.StringComparison.CurrentCultureIgnoreCase or System.StringComparison.OrdinalIgnoreCase.

When you search for a single character, use the char overload of Contains. It avoids allocating a one-character string and is more direct:

sample · csharpdocs/csharp/fundamentals/strings/common-tasks/snippets/search/Program.csContainsChar

csharp
namespace SearchStrings;

public static class Program
{
    public static void Main()
    {
        Contains();
        Console.WriteLine();
        ContainsChar();
        Console.WriteLine();
        IndexOfExample();
    }

    private static void Contains()
    {
        // <contains>
        string factMessage = "Extension methods have all the capabilities of regular static methods.";

        // Write the string and include the quotation marks.
        Console.WriteLine($"\"{factMessage}\"");

        // Default comparisons are case sensitive.
        bool containsSearchResult = factMessage.Contains("extension");
        Console.WriteLine($"""Contains "extension"? {containsSearchResult}""");

        // For user-facing searches, pass a StringComparison value to control case and culture.
        bool ignoreCaseSearchResult = factMessage.StartsWith("extension", StringComparison.CurrentCultureIgnoreCase);
        Console.WriteLine($"""Starts with "extension"? {ignoreCaseSearchResult} (ignoring case)""");

        bool endsWithSearchResult = factMessage.EndsWith(".", StringComparison.Ordinal);
        Console.WriteLine($"Ends with '.'? {endsWithSearchResult}");
        // => "Extension methods have all the capabilities of regular static methods."
        // => Contains "extension"? False
        // => Starts with "extension"? True (ignoring case)
        // => Ends with '.'? True
        // </contains>
    }

    private static void ContainsChar()
    {
        // <ContainsChar>
        string path = "/usr/local/bin";
        bool hasSlash = path.Contains('/');
        Console.WriteLine($"Path contains '/': {hasSlash}");
        // => Path contains '/': True
        // </ContainsChar>
    }

    private static void IndexOfExample()
    {
        // <IndexOf>
        string factMessage = "Extension methods have all the capabilities of regular static methods.";

        Console.WriteLine($"\"{factMessage}\"");

        // Extract the text between the first and last occurrence of "methods".
        int first = factMessage.IndexOf("methods") + "methods".Length;
        int last = factMessage.LastIndexOf("methods");
        string between = factMessage.Substring(first, last - first);
        Console.WriteLine($"""Substring between "methods" and "methods": '{between}'""");
        // => "Extension methods have all the capabilities of regular static methods."
        // => Substring between "methods" and "methods": ' have all the capabilities of regular static '
        // </IndexOf>
    }
}

Locate the position of text

IndexOf returns the zero-based index of the first occurrence of a substring (or character), and LastIndexOf returns the index of the last occurrence. Both return -1 when the search text isn't present. Combine them to extract the text between two markers:

sample · csharpdocs/csharp/fundamentals/strings/common-tasks/snippets/search/Program.csIndexOf

csharp
namespace SearchStrings;

public static class Program
{
    public static void Main()
    {
        Contains();
        Console.WriteLine();
        ContainsChar();
        Console.WriteLine();
        IndexOfExample();
    }

    private static void Contains()
    {
        // <contains>
        string factMessage = "Extension methods have all the capabilities of regular static methods.";

        // Write the string and include the quotation marks.
        Console.WriteLine($"\"{factMessage}\"");

        // Default comparisons are case sensitive.
        bool containsSearchResult = factMessage.Contains("extension");
        Console.WriteLine($"""Contains "extension"? {containsSearchResult}""");

        // For user-facing searches, pass a StringComparison value to control case and culture.
        bool ignoreCaseSearchResult = factMessage.StartsWith("extension", StringComparison.CurrentCultureIgnoreCase);
        Console.WriteLine($"""Starts with "extension"? {ignoreCaseSearchResult} (ignoring case)""");

        bool endsWithSearchResult = factMessage.EndsWith(".", StringComparison.Ordinal);
        Console.WriteLine($"Ends with '.'? {endsWithSearchResult}");
        // => "Extension methods have all the capabilities of regular static methods."
        // => Contains "extension"? False
        // => Starts with "extension"? True (ignoring case)
        // => Ends with '.'? True
        // </contains>
    }

    private static void ContainsChar()
    {
        // <ContainsChar>
        string path = "/usr/local/bin";
        bool hasSlash = path.Contains('/');
        Console.WriteLine($"Path contains '/': {hasSlash}");
        // => Path contains '/': True
        // </ContainsChar>
    }

    private static void IndexOfExample()
    {
        // <IndexOf>
        string factMessage = "Extension methods have all the capabilities of regular static methods.";

        Console.WriteLine($"\"{factMessage}\"");

        // Extract the text between the first and last occurrence of "methods".
        int first = factMessage.IndexOf("methods") + "methods".Length;
        int last = factMessage.LastIndexOf("methods");
        string between = factMessage.Substring(first, last - first);
        Console.WriteLine($"""Substring between "methods" and "methods": '{between}'""");
        // => "Extension methods have all the capabilities of regular static methods."
        // => Substring between "methods" and "methods": ' have all the capabilities of regular static '
        // </IndexOf>
    }
}

When you need every occurrence rather than the first or last, iterate by passing the previous result plus one as the startIndex argument, or switch to a regular expression.

Choose the right comparison

Most search overloads accept an optional System.StringComparison value. Pick it based on the kind of data you're searching:

Ordinal comparison is the fastest option and the right default for anything that isn't natural-language text. Culture-aware comparison is significantly slower and can produce surprising results. For example, in some cultures the lowercase i doesn't match an uppercase I.Reserve it for searches that users perform against prose.

For an in-depth treatment of culture-aware comparison, see Best practices for comparing strings.

See also