常量字符串上的模式匹配 Span<char>

注意

本文是特性规范。 此规范是功能的设计文档。 它包括建议的规范变更,以及功能设计和开发过程中所需的信息。 这些文章将持续发布,直至建议的规范变更最终确定并纳入当前的 ECMA 规范。

功能规范与已完成的实现之间可能存在一些差异。 这些差异记录在相关的语言设计会议 (LDM) 说明中。

可以在有关规范的文章中了解更多有关将功能规范子块纳入 C# 语言标准的过程。

支持者问题:https://github.com/dotnet/csharplang/issues/8640

总结

允许在常量字符串上对 Span<char>ReadOnlySpan<char> 进行模式匹配。

动机

为了性能,在许多情况下,Span<char>ReadOnlySpan<char> 的使用优于字符串。 框架添加了许多新 API,使你能够使用 ReadOnlySpan<char> 代替 string

对字符串的一个常见操作是使用开关来测试它是否是特定值,编译器会优化这样的开关。 然而,目前除了手动实现开关和优化外,没有其他方法可以有效地在 ReadOnlySpan<char> 上执行相同的操作。

为了鼓励采用 ReadOnlySpan<char>,我们允许在常量 ReadOnlySpan<char> 上对 string 进行模式匹配,从而也允许在 switch 中使用它。

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

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

详细设计

我们对规范的常量模式修改如下(建议添加的部分以粗体显示):

给定一个模式输入值 e 和一个常量模式 P,其转换值为 v

  • 如果 e 具有整数类型或枚举类型,或其中一种可为 null 形式,并且 v 具有整数类型,则如果表达式 e == v 的结果为 true,则模式P匹配e;否则
  • 如果 e 的类型为 System.Span<char>System.ReadOnlySpan<char>,并且 c 是常量字符串,并且 c 没有常量值 null,则如果 System.MemoryExtensions.SequenceEqual<char>(e, System.MemoryExtensions.AsSpan(c)) 返回 true,则模式被视为匹配。
  • P 返回 时,模式 object.Equals(e, v) 与值 true 匹配。

知名成员

System.Span<T>System.ReadOnlySpan<T> 按名称匹配,必须是 ref struct,并且可以在 corlib 外部定义。

System.MemoryExtensions 按名称匹配,并且可以在 corlib 外部定义。

System.MemoryExtensions.SequenceEqual 重载的签名必须匹配:

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

System.MemoryExtensions.AsSpan 的签名必须匹配:

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

具有可选参数的方法不在考虑范围内。

缺点

替代方案

未解决的问题

  1. 是否应独立定义匹配,而非依赖于像 MemoryExtensions.SequenceEqual() 这样的元素?

    ... 如果 e.Length == c.Length 中所有字符对于 e[i] == c[i]e 都匹配,则模式被视为匹配。

    建议:为提高性能,按照 MemoryExtensions.SequenceEqual() 的方式进行定义。 如果缺少 MemoryExtensions,则报告编译错误。

  2. 是否应该允许与 (string)null 匹配?

    如果是这样,那么 (string)null 是否应该包含 "",因为 MemoryExtensions.AsSpan(null) == MemoryExtensions.AsSpan("")

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

    建议:常量模式 (string)null 应报告为错误。

  3. 常量模式匹配是否应包括对 Span<char>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?
    }
    

    建议:没有常量模式的隐式运行时类型测试。 (允许 IsABC<T>() 示例,因为类型测试是显式的。

    未实施此建议。 前面的所有示例都会产生编译器错误。

  4. 包含是否应考虑常量字符串模式、列表模式和 Length 属性模式?

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

    建议:与表达式值为 string 时使用的包含行为相同。 (这是否意味着常量字符串、列表模式和 Length 之间没有包含,而是将 [..] 视为匹配任何项?)

设计会议

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