Operações de string: Correspondência de padrões, desempenho e pesquisa baseada em span

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

Encontre texto específico usando expressões regulares

A System.Text.RegularExpressions.Regex classe procura padrões em cadeias em vez de substrings fixas. O método estático Regex.IsMatch recebe a cadeia de entrada, um padrão e flags opcionais RegexOptions .

O exemplo seguinte procura em cada frase a palavra the ou their, insensível a maiúsculas. O padrão the(ir)?\s corresponde the opcionalmente seguido de ir, depois um carácter de espaço em branco:

Pattern Meaning
the corresponder ao texto literal the
(ir)? Jogo 0 ou 1 ocorrência de ir
\s Corresponder a um carácter 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 as cordas contra um padrão

Para verificar se uma entrada inteira corresponde a uma forma, ancorar o padrão com ^ e $. O exemplo seguinte valida que cada cadeia é um número de telefone ao 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 corda
\d{3} corresponde exatamente a caracteres de três dígitos
- corresponder a um carácter literal -
\d{4} corresponde exatamente a caracteres de quatro dígitos
$ Coincidir com a extremidade da corda
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 a sintaxe completa dos padrões, veja Linguagem de expressões regulares - referência rápida.

Escolha entre string métodos e expressões regulares

string métodos e Regex resolução de problemas sobrepostos. Prefira string métodos quando o texto que procura é um valor literal, um prefixo ou sufixo conhecido, ou um delimitador fixo. São mais fáceis de ler e rápidos, porque não pagam o custo de compilar e executar um padrão. Alcance para Regex quando o alvo da pesquisa é uma forma, como alternâncias, grupos opcionais, classes de caracteres repetidas ou validação ancorada. Como regra geral, se conseguir escrever a pesquisa como uma ou duas string.Contains / / StartsWithIndexOf chamadas, faça-o.

Pesquisa usando ReadOnlySpan<char>

Quando analisa entradas grandes ou executa uma pesquisa num caminho quente, as alocações por chamada de string.Substring e string.Split podem dominar. ReadOnlySpan<char> Dá-lhe uma visão sobre uma string existente (ou array, ou stack buffer) sem copiar, e MemoryExtensions fornece equivalentes baseados em span 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 expansão evita alocações porque as fatias (input[start..], rest[..end]) são simplesmente janelas sobre os caracteres originais. A mesma abordagem escala para analisar listas-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) o padrão para ordinal, mas String.Compare(String, String) e String.IndexOf(String) padrão para a cultura atual. Esta diferença é importante de duas formas:

  • Velocidade. A comparação ordinal é um teste byte por byte que corre em laços vetorializados e apertados. A comparação consciente da cultura consulta uma tabela de ordenação, caminha combinando personagens e aplica regras específicas do local. Para a mesma entrada, pode ser uma ordem de grandeza mais lenta.
  • Exatidão. A comparação consciente da cultura pode dobrar caracteres que não esperas (turco i/I, alemão ß para ss, ligaduras). Este comportamento é correto para ordenar nomes que um utilizador vê, mas errado para analisar identificadores, caminhos ou tokens de protocolo.

Para texto definido por máquina, como nomes de ficheiros, URLs, cabeçalhos HTTP, identificadores e chaves de configuração, passe StringComparison.Ordinal ou StringComparison.OrdinalIgnoreCase explicitamente. Reserve valores conscientes da cultura para texto em linguagem natural mostrado aos utilizadores. Para orientações completas, consulte Melhores práticas para comparar cadeias em .NET.

Ver também