PAYATHON 2026

.NET 8 正则表达式如何确保 CODE 非空后再捕获 NAME?

数据阿明

结论

问题主要出在 CODE 中的 [a-z]?。其中的 ? 允许小写字母出现零次,因此整个 CODE 可以匹配空字符串。将它改为 [a-z],就要求该分支捕获一个小写字母。

另外,[B|M] 会匹配 B、| 或 M。如果只允许 B 和 M,应改为 [BM]。

假设每次处理一个不含换行符的字符串,允许开头有空格或制表符,并保留原表达式中 CODE 可以是一个小写字母的规则,可以使用:

\A[ \t]*(?<CODE>X[BM]\d{6}[A-Z]|[a-z])[ \t]+(?<NAME>[^\r\n]+)\z

为什么看起来只捕获了 NAME?

原表达式中的 CODE 有两个分支:

[X][B|M]\d{6}[A-Z]

以及:

[a-z]?

第二个分支允许匹配空字符串。假设实际输入开头有两个空格:

  FOB NUMBER

正则引擎可以通过回溯完成以下匹配:

  1. 开头的 \s+ 消耗一个空格。
  2. CODE 通过 [a-z]? 匹配空字符串。
  3. CODE 后面的 \s+ 消耗另一个空格。
  4. NAME 捕获 FOB NUMBER。

此时 CODE 捕获的是空字符串。在 .NET 中,这种成功的空捕获可以表现为 Groups["CODE"].Success 为 true,但 Value 为 ""。因此,不能只凭 Success 判断 CODE 是否有内容。

.+ 会接收剩余内容,而 CODE 可以为空是因为 [a-z]? 允许匹配空字符串。

原表达式与展示的样例存在差异

原表达式以 ^\s+ 开头,要求字符串开头至少有一个空白字符。按题目展示的内容,两行都没有前导空白,因此直接逐行使用原表达式时,两行都应匹配失败。

如果实际程序出现了所描述的结果,需要检查输入是否保留了缩进、是否包含换行,以及是否设置了 RegexOptions.Multiline。此外,\s 可以匹配换行符,用它分隔字段时可能跨过行边界。

修正后的表达式使用 [ \t],将字段间的空白限定为空格和制表符;使用 \A 和 \z,要求匹配整个输入字符串。

在 .NET 8 中使用

using System;
using System.Text.RegularExpressions;

const string pattern =
    @"\A[ \t]*(?<CODE>X[BM]\d{6}[A-Z]|[a-z])[ \t]+(?<NAME>[^\r\n]+)\z";

string[] inputs =
{
    "XM204000B 200 SUNSHINE RD & PALACE",
    "FOB NUMBER"
};

foreach (string input in inputs)
{
    Match match = Regex.Match(input, pattern);

    if (!match.Success)
    {
        Console.WriteLine($"匹配失败:{input}");
        continue;
    }

    Console.WriteLine($"CODE: {match.Groups["CODE"].Value}");
    Console.WriteLine($"NAME: {match.Groups["NAME"].Value}");
}

按照该表达式,第一行的 CODE 为 XM204000B,NAME 为 200 SUNSHINE RD & PALACE;第二行因缺少有效 CODE 而匹配失败。

根据实际 CODE 格式调整

如果 CODE 只能是 XM204000B 这一类格式,不允许单个小写字母,应删除整个小写字母分支:

\A[ \t]*(?<CODE>X[BM]\d{6}[A-Z])[ \t]+(?<NAME>[^\r\n]+)\z

如果六位数字必须是 ASCII 数字 0–9,将 \d{6} 改为 [0-9]{6},因为 .NET 默认的 \d 也能匹配其他 Unicode 十进制数字。

上述表达式要求 NAME 至少包含一个非换行字符,但允许 NAME 全部为空格。如果业务要求 NAME 必须包含非空白字符,还需要增加相应校验。处理多行文本时,应先逐行读取,再对每行应用表达式。

备注:内容仅供参考。