Compartilhar via


Correspondência de padrões de 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. Ela inclui alterações de especificação propostas, juntamente com as informações necessárias durante o design e o desenvolvimento do recurso. Esses artigos são publicados até que as alterações de especificação propostas sejam finalizadas e incorporadas na especificação ECMA atual.

Pode haver algumas divergências entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da reunião de design de idioma (LDM).

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

Problema do especialista: https://github.com/dotnet/csharplang/issues/8640

Resumo

Permitir a correspondência de padrões de Span<char> e ReadOnlySpan<char> em uma cadeia de caracteres constante.

Motivação

Para fins de desempenho, o uso de Span<char> e ReadOnlySpan<char> é preferido em vez de cadeias de caracteres 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á como fazer o mesmo em um ReadOnlySpan<char> com eficiência, além de implementar a opção e a otimização manualmente.

Para incentivar a adoção do ReadOnlySpan<char>, permitimos a correspondência de padrões de um ReadOnlySpan<char>, em um stringconstante, permitindo assim que também seja utilizado em uma troca.

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 para padrões constantes da seguinte maneira (a adição proposta é mostrada em negrito):

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

  • se e tiver tipo integral ou tipo de enumeração, ou uma forma anulável de um desses, e v tiver tipo integral, o padrão Pcorresponderá ao valor e se o resultado da expressão e == v for 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, o padrão será considerado correspondente se System.MemoryExtensions.SequenceEqual<char>(e, System.MemoryExtensions.AsSpan(c)) retornar true.
  • o padrão Pcorresponderá ao valor e se object.Equals(e, v) retornar true.

Membros conhecidos

System.Span<T> e System.ReadOnlySpan<T> são correspondidos por nomes; devem ser ref structs e podem ser definidos fora do corlib.

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

A assinatura de 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 deve corresponder:

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

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

Desvantagens

Nenhum

Alternativas

Nenhum

Perguntas não resolvidas

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

    ... o padrão é considerado uma correspondência se e.Length == c.Length e e[i] == c[i] forem iguais para todos os caracteres em e.

    Recomendação: definir em termos de MemoryExtensions.SequenceEqual() para desempenho. Se MemoryExtensions estiver ausente, relate o erro de compilação.

  2. A correspondência com (string)null deve ser permitida?

    Se sim, (string)null deve se subordinar a "", visto que MemoryExtensions.AsSpan(null) == MemoryExtensions.AsSpan("")?

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

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

  3. A correspondência constante de padrões deve incluir um teste de tipo de runtime do valor da expressão para 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 runtime 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 subordinação deve considerar padrões de cadeia de caracteres constantes, padrões de listas e padrões 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á subordinação entre cadeias de caracteres constantes, padrões de lista e Length, além de tratar [..] como correspondentes a qualquer um?)

Reuniões de design

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