Compartir a través de


Coincidencia de patrones de Span<char> en una cadena de constante

Nota:

Este artículo es una especificación de características. La especificación actúa como documento de diseño de la característica. Incluye cambios de especificación propuestos, junto con la información necesaria durante el diseño y el desarrollo de la característica. Estos artículos se publican hasta que se finalizan los cambios de especificación propuestos y se incorporan en la especificación ECMA actual.

Puede haber algunas discrepancias entre la especificación de características y la implementación completada. Esas diferencias se recogen en las notas de la reunión de diseño de lenguaje (LDM) correspondientes.

Puede obtener más información sobre el proceso de adopción de especificaciones de características en el estándar del lenguaje C#, en el artículo sobre especificaciones.

Problema planteado por el experto: https://github.com/dotnet/csharplang/issues/8640

Resumen

Permitir la coincidencia de un patrón Span<char> y un ReadOnlySpan<char> en una cadena constante.

Motivación

Para el rendimiento, se prefiere el uso de Span<char> y ReadOnlySpan<char> sobre la cadena de caracteres en muchos escenarios. El marco ha añadido muchas API nuevas para permitirte usar ReadOnlySpan<char> en lugar de un string.

Una operación común en cadenas es utilizar un conmutador para comprobar si es un valor determinado, y el compilador optimiza dicho conmutador. Sin embargo, actualmente no hay manera de hacer lo mismo en un ReadOnlySpan<char> de manera eficiente, aparte de implementar el interruptor y la optimización manualmente.

Para fomentar la adopción de ReadOnlySpan<char> permitimos que el patrón coincida con un ReadOnlySpan<char>, en una constante string, lo que también permite su uso en un conmutador.

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

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

Diseño detallado

Modificamos la spec para patrones constantes de la siguiente manera (la adición propuesta se muestra en negrita):

Dado un valor de patrón de entrada e y un patrón constante P, cuyo valor convertido es v,

  • si e tiene tipo entero o tipo de enumeración, o una forma que acepta valores NULL de uno de ellos y v tiene un tipo entero, el patrón Pcoincide con el valor e si el resultado de la expresión e == v es true; de otra manera
  • Si e es de tipo System.Span<char> o System.ReadOnlySpan<char>, y c es una cadena constante, y c no tiene un valor constante de null, entonces el patrón se considera coincidente si System.MemoryExtensions.SequenceEqual<char>(e, System.MemoryExtensions.AsSpan(c)) devuelve true.
  • el patrón Pcoincide con el valor e si object.Equals(e, v) devuelve true.

Miembros conocidos

System.Span<T> y System.ReadOnlySpan<T> coinciden por nombre, deben ser ref struct, y pueden definirse fuera de corlib.

System.MemoryExtensions coincide con el nombre y se puede definir fuera de corlib.

La firma de las sobrecargas de System.MemoryExtensions.SequenceEqual debe coincidir con:

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

La firma de System.MemoryExtensions.AsSpan debe coincidir:

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

Los métodos con parámetros opcionales se excluyen de la consideración.

Inconvenientes

Ninguna

Alternativas

Ninguna

Preguntas sin resolver

  1. ¿Debe definirse la coincidencia independientemente de MemoryExtensions.SequenceEqual() etc.?

    ... el patrón se considera coincidente si e.Length == c.Length y e[i] == c[i] para todos los caracteres de e.

    Recomendación: defina en términos de MemoryExtensions.SequenceEqual() para el rendimiento. Si falta MemoryExtensions, informe de error de compilación.

  2. ¿Se debe permitir la coincidencia con (string)null?

    Si es así, ¿debe (string)null subsumir a "" ya que MemoryExtensions.AsSpan(null) == MemoryExtensions.AsSpan("")?

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

    Recomendación: El patrón constante (string)null debería notificarse como error.

  3. ¿Debe la coincidencia de patrones constantes incluir una prueba de tipo en tiempo de ejecución del valor de expresión para Span<char> o 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?
    }
    

    Recomendación: No hay prueba de tipo implícita en tiempo de ejecución para el patrón constante. (El ejemploIsABC<T>() se permite porque la prueba de tipo es explícita).

    Esta recomendación no se ha implementado. Todos los ejemplos anteriores producen un error de compilador.

  4. ¿Debe la subsumpción considerar los patrones de cadena constantes, los patrones de lista y el patrón de propiedad Length?

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

    Recomendación: usar el mismo comportamiento de subsunción que se emplea cuando el valor de la expresión es string. (¿Esto significa que no hay subsumición entre cadenas constantes, patrones de lista y Length, aparte de tratar [..] como que coincide con cualquiera?)

Reuniones de diseño

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