Partilhar via


Correspondência de padrão Span<char> em uma cadeia de caracteres constante

Observação

Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele inclui mudanças de especificação propostas, juntamente com as informações necessárias durante o design e desenvolvimento do recurso. Estes artigos são publicados até que as alterações de especificações propostas sejam finalizadas e incorporadas na especificação ECMA atual.

Pode haver algumas discrepâncias entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da Language Design Meeting (LDM).

Você pode saber mais sobre o processo de adoção de especificações de recursos no padrão de linguagem C# no artigo sobre as especificações .

Resumo

Permite que o padrão corresponda a um Span<char> e a um ReadOnlySpan<char> em uma cadeia de caracteres constante.

Motivação

Para um melhor desempenho, o uso de Span<char> e ReadOnlySpan<char> é preferível a strings em muitos cenários. A estrutura adicionou muitas APIs novas para permitir que você use ReadOnlySpan<char> no lugar de um string.

Uma operação comum em strings é usar um switch para testar se é um valor específico, e o compilador otimiza esse switch. No entanto, atualmente não há nenhuma maneira de fazer o mesmo em um ReadOnlySpan<char> de forma eficiente, além de implementar o switch e a otimização manualmente.

A fim de incentivar a adoção de ReadOnlySpan<char> permitimos a correspondência de padrões de um ReadOnlySpan<char>, em uma stringconstante, permitindo assim também que ele seja usado em um interruptor.

static bool Is123(ReadOnlySpan<char> s)
{
    return s is "123";
}

static bool IsABC(Span<char> s)
{
    return s switch { "ABC" => true, _ => false };
}

Projeto detalhado

Alteramos a especificação de para padrões constantes da seguinte forma (a adição proposta é evidenciada em negrito):

Dado um valor de entrada padrão e e um padrão constante P com valor convertido v,

  • se e têm tipo integral ou tipo enum, ou uma forma anulável de um destes, e v tem tipo integral, o padrão Pcombina com o valor e se o resultado da expressão e == v é true; caso contrário,
  • Se e for do tipo System.Span<char> ou System.ReadOnlySpan<char>, e c for uma cadeia de caracteres constante, e c não tiver um valor constante de null, então o padrão será considerado correspondente se System.MemoryExtensions.SequenceEqual<char>(e, System.MemoryExtensions.AsSpan(c)) retornar true.
  • O padrão Pcorresponde o valor e se object.Equals(e, v) retornar true.

Membros bem conhecidos

System.Span<T> e System.ReadOnlySpan<T> são correspondidos pelo nome, devem ser ref structs e podem ser definidos fora de corlib.

System.MemoryExtensions é emparelhado pelo nome e pode ser definido fora do corlib.

A assinatura das sobrecargas de System.MemoryExtensions.SequenceEqual deve corresponder:

  • public static bool SequenceEqual<T>(System.Span<T>, System.ReadOnlySpan<T>)
  • public static bool SequenceEqual<T>(System.ReadOnlySpan<T>, System.ReadOnlySpan<T>)

A assinatura de System.MemoryExtensions.AsSpan precisa corresponder a:

  • public static System.ReadOnlySpan<char> AsSpan(string)

Os métodos com parâmetros opcionais são excluídos da consideração.

Desvantagens

Nenhum

Alternativas

Nenhum

Questões por resolver

  1. A correspondência deve ser definida independentemente de MemoryExtensions.SequenceEqual() etc.?

    ... o padrão é considerado igual se e.Length == c.Length e e[i] == c[i] forem idênticos para todos os caracteres em e.

    Recomendação: Definir em termos de MemoryExtensions.SequenceEqual() para otimizar o desempenho. Se MemoryExtensions estiver faltando, informe erro de compilação.

  2. Deve ser permitida a correspondência com (string)null?

    Em caso afirmativo, deve (string)null subsumir "" desde MemoryExtensions.AsSpan(null) == MemoryExtensions.AsSpan("")?

    static bool IsEmpty(ReadOnlySpan<char> span)
    {
        return span switch
        {
            (string)null => true, // ok?
            "" => true,           // error: unreachable?
            _ => false,
        };
    }
    

    Recomendação: Padrão constante (string)null deve ser relatado como um erro.

  3. Deve a correspondência de padrão constante incluir um teste de tipo em tempo de execução para o valor da expressão Span<char> ou ReadOnlySpan<char>?

    static bool Is123<T>(Span<T> s)
    {
        return s is "123"; // test for Span<char>?
    }
    
    static bool IsABC<T>(Span<T> s)
    {
        return s is Span<char> and "ABC"; // ok?
    }
    
    static bool IsEmptyString<T>(T t) where T : ref struct
    {
        return t is ""; // test for ReadOnlySpan<char>, Span<char>, string?
    }
    

    Recomendação: Nenhum teste de tipo de tempo de execução implícito para padrão constante. (IsABC<T>() exemplo é permitido porque o teste de tipo é explícito.)

    Esta recomendação não foi implementada. Todos os exemplos anteriores produzem um erro de compilador.

  4. A subsunção deve considerar padrões de cadeia de caracteres constantes, padrões de lista e padrão de propriedade Length?

    static int ToNum(ReadOnlySpan<char> s)
    {
        return s switch
        {
            { Length: 0 } => 0,
            "" => 1,        // error: unreachable?
            ['A',..] => 2,
            "ABC" => 3,     // error: unreachable?
            _ => 4,
        };
    }
    

    Recomendação: mesmo comportamento de subsunção usado quando o valor da expressão é string. (Isso significa que não há subsunção entre cadeias de caracteres constantes, padrões de lista e Length, além de tratar [..] como correspondendo a qualquer?)

Reuniões de design

https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-07.md#readonlyspanchar-patterns