Operações de cadeia de caracteres: correspondência de padrões, desempenho e pesquisa baseada em span

Este artigo aborda três operações de cadeia de caracteres: correspondência de padrões de expressão regular com System.Text.RegularExpressions.Regex, pesquisa ReadOnlySpan<T>sem alocação e escolha de um StringComparison valor para comparações corretas e rápidas.

Localizar texto específico usando expressões regulares

A System.Text.RegularExpressions.Regex classe pesquisa cadeias de caracteres em busca de padrões em vez de subcadeias de caracteres fixas. O método estático Regex.IsMatch usa a cadeia de caracteres de entrada, um padrão e sinalizadores opcionais RegexOptions .

O exemplo a seguir pesquisa cada frase para a palavra que não diferencia maiúsculas de minúsculas. O padrão the(ir)?\s corresponde the opcionalmente seguido por irum caractere de espaço em branco:

Pattern Meaning
the corresponder ao texto literal the
(ir)? corresponder a 0 ou 1 ocorrência de ir
\s corresponder a um caractere de espaço em branco
string[] sentences =
[
    "Put the water over there.",
    "They're quite thirsty.",
    "Their water bottles broke."
];

string pattern = @"the(ir)?\s";

foreach (string s in sentences)
{
    Console.Write($"{s,28}");

    if (Regex.IsMatch(s, pattern, RegexOptions.IgnoreCase))
    {
        Console.WriteLine($"  (match for '{pattern}' found)");
    }
    else
    {
        Console.WriteLine();
    }
}

Validar cadeias de caracteres em um padrão

Para verificar se uma entrada inteira corresponde a uma forma, ancore o padrão com ^ e $. O exemplo a seguir valida que cada cadeia de caracteres é um número de telefone no estilo dos EUA: três dígitos, três dígitos, quatro dígitos, separados por traços:

Pattern Meaning
^ corresponder ao início da cadeia de caracteres
\d{3} corresponder exatamente a três dígitos de caracteres
- corresponder a um caractere literal -
\d{4} corresponder exatamente a quatro dígitos caracteres
$ corresponder ao final da cadeia de caracteres
string[] numbers =
[
    "123-555-0190",
    "444-234-22450",
    "690-555-0178",
    "146-893-232",
    "146-555-0122",
    "4007-555-0111",
    "407-555-0111",
    "407-2-5555",
    "407-555-8974",
    "407-2ab-5555",
    "690-555-8148",
    "146-893-232-"
];

string pattern = """^\d{3}-\d{3}-\d{4}$""";

foreach (string s in numbers)
{
    Console.Write($"{s,14}");
    Console.WriteLine(Regex.IsMatch(s, pattern) ? " - valid" : " - invalid");
}

Para obter a sintaxe de padrão completa, consulte a linguagem de expressão Regular – referência rápida.

Escolher entre string métodos e expressões regulares

string métodos e Regex resolver problemas sobrepostos. Prefira string métodos quando o texto que você está procurando é um valor literal, um prefixo ou sufixo conhecido ou um delimitador fixo. Eles são mais simples de ler e mais rápido, porque não pagam o custo de compilar e executar um padrão. Alcance quando Regex o destino de pesquisa for uma forma, como alternâncias, grupos opcionais, classes de caracteres repetidas ou validação ancorada. Como regra geral, se você puder escrever a pesquisa como uma ou duas string.Contains / / StartsWithIndexOf chamadas, faça isso.

Pesquisar usando ReadOnlySpan<char>

Quando você analisa entradas grandes ou executa uma pesquisa em um caminho quente, as alocações string.Substringstring.Split por chamada podem dominar. ReadOnlySpan<char> fornece uma exibição sobre uma cadeia de caracteres existente (ou matriz ou buffer de pilha) sem copiar e MemoryExtensions fornece equivalentes baseados em intervalo dos métodos comuns string , incluindo IndexOf:

ReadOnlySpan<char> input = "key1=alpha;key2=beta;key3=gamma".AsSpan();
ReadOnlySpan<char> needle = "key2=".AsSpan();

int start = input.IndexOf(needle);
if (start >= 0)
{
    ReadOnlySpan<char> rest = input[(start + needle.Length)..];
    int end = rest.IndexOf(';');
    ReadOnlySpan<char> value = end >= 0 ? rest[..end] : rest;
    Console.WriteLine($"key2 = {value}");
}
// => key2 = beta

A pesquisa baseada em span evita alocações porque as fatias (input[start..], rest[..end]) são simplesmente janelas sobre os caracteres originais. A mesma abordagem é dimensionada para analisar listas de chave-valor, cabeçalhos e outros textos delimitados sem nunca chamar Substring.

Considerações de desempenho para StringComparison

A maioria dos string métodos de instância tem sobrecargas que aceitam um StringComparison valor. Métodos como String.Equals(String) padrão para ordinal, mas String.Compare(String, String) e String.IndexOf(String) padrão para a cultura atual. Essa diferença importa de duas maneiras:

  • Velocidade. A comparação ordinal é um teste byte-for-byte que é executado em loops apertados e vetorizados. A comparação com reconhecimento de cultura consulta uma tabela de classificação, orienta a combinação de caracteres e aplica regras específicas à localidade. Para a mesma entrada, pode ser uma ordem de magnitude mais lenta.
  • Exatidão. A comparação com reconhecimento de cultura pode dobrar caracteres que você não espera (turco i/I, alemão ß para ss, ligaturas). Esse comportamento é adequado para classificar nomes que um usuário vê, mas errado para analisar identificadores, caminhos ou tokens de protocolo.

Para texto definido pelo computador, como nomes de arquivo, URLs, cabeçalhos HTTP, identificadores e chaves de configuração, passe StringComparison.Ordinal ou StringComparison.OrdinalIgnoreCase explicitamente. Reserve valores com reconhecimento de cultura para texto em linguagem natural mostrado aos usuários. Para obter diretrizes abrangentes, consulte as práticas Best para comparar cadeias de caracteres em .NET.

Consulte também