我需要验证要检索的属性名称的用户输入。
例如,用户可以为 Windows 窗体控件对象键入“Parent.Container”属性或仅键入“Name”属性。然后我使用反射来获取属性的值。
我需要的是检查用户是否输入了 c# 属性的合法符号(或者只是像 \w 这样的合法单词符号),并且这个属性是否可以复合(包含两个或多个用点分隔的单词)。
我现在有这个,这是一个正确的解决方案吗?
^([\w]+\.)+[\w]+$|([\w]+)
Run Code Online (Sandbox Code Playgroud)
我使用了Regex.IsMatch方法,true当我通过"?someproperty"时它返回,尽管 "\w" 不包括 "?"
我也在寻找这个,但我知道现有的答案都不完整。经过一番挖掘,这就是我发现的。
首先我们需要知道我们想要哪个有效:根据运行时有效还是根据语言有效?例子:
Foo\u0123Bar是 C#语言的有效属性名称,但不适用于运行时。编译器会消除差异,编译器会悄悄地将标识符转换为Foo?Bar.@前缀),语言将@视为标识符的一部分,但运行时看不到它。根据您的需要,两者都可能有意义。如果您将经过验证的文本提供给反射方法,例如GetProperty(string),您将需要运行时有效版本。但是,如果您想要 C# 开发人员更熟悉的语法,则需要语言- 有效版本。
C# 版本 5 是(截至 7/2018)具有正式标准的最新版本:ECMA 334规范。它的规则说:
本小节中给出的标识符规则与 Unicode 标准附件 15 推荐的规则完全对应,除了允许下划线作为初始字符(在 C 编程语言中是传统的),标识符中允许使用 Unicode 转义序列,以及“ @” 字符被允许作为前缀,使关键字能够用作标识符。
提到的“Unicode Standard Annex 15”是Unicode TR 15, Annex 7,其将基本模式形式化为:
<identifier> ::= <identifier_start> ( <identifier_start> | <identifier_extend> )*
<identifier_start> ::= [{Lu}{Ll}{Lt}{Lm}{Lo}{Nl}]
<identifier_extend> ::= [{Mn}{Mc}{Nd}{Pc}{Cf}]
Run Code Online (Sandbox Code Playgroud)
{大括号中的代码} 是 Unicode 类,它们通过\p{category}. 因此(稍微简化后)根据运行时检查“有效”的基本正则表达式将是:
@"^[\p{L}\p{Nl}_][\p{Cf}\p{L}\p{Mc}\p{Mn}\p{Nd}\p{Nl}\p{Pc}]*$"
Run Code Online (Sandbox Code Playgroud)
C# 规范还要求标识符采用 Unicode 规范化形式 C。不过,它并不要求编译器实际执行它。至少 Roslyn C# 编译器允许非范式标识符(例如,E\u0304\u0306)并将它们视为与等效的范式标识符(例如\u0100\u0306)不同。无论如何,据我所知,没有理智的方式用正则表达式来表示这样的规则。如果您不需要/希望用户能够区分看起来完全相同的属性,我的建议是只运行string.Normalize()用户的输入来完成它。
C# 规范说,如果两个标识符仅在格式字符上有所不同,则它们是等效的。例如,Elmo(四个字符)和Elmo(El\u00ADmo)是同一个标识符。(注意:这是软连字符,它通常是不可见的;不过有些字体可能会显示它。)如果不可见字符的存在会给您带来麻烦,您可以\p{Cf}从正则表达式中删除。这不会减少您接受的标识符——只是您接受的格式。
C# 规范保留包含“__”的标识符供自己使用。根据您的需要,您可能希望排除它。这应该是一个独立于正则表达式的操作。
反射、Type、 IL 以及可能其他地方有时会显示带有额外符号的类名或方法名。例如,类型名称可以指定为X`1+Y[T]。这些额外的东西不是标识符的一部分——它是一种表示类型信息的无关方式。
这只是之前的正则表达式,但也允许:
@第一个是一个微不足道的修改:只需添加@?.
Unicode 转义序列的形式为@"\\[Uu][\dA-Fa-f]{4}". 我们可能很想把它塞进两个[...]对中并称之为完成,但这会错误地允许(例如)\u0000作为标识符。我们需要将转义序列限制为产生其他可接受字符的转义序列。一种方法是进行预传递以转换转义序列:用\\[Uu][\dA-Fa-f]{4}相应的字符替换所有字符。
因此,将所有这些放在一起,从 C#语言的角度检查字符串是否有效将是:
bool IsValidIdentifier(string input)
{
if (input is null) { throw new ArgumentNullException(); }
// Technically the input must be in normal form C. Implementations aren't required
// to verify that though, so you could remove this check if your runtime doesn't
// mind.
if (!input.IsNormalized())
{
return false;
}
// Convert escape sequences to the characters they represent. The only allowed escape
// sequences are of form \u0000 or \U0000, where 0 is a hex digit.
MatchEvaluator replacer = (Match match) =>
{
string hex = match.Groups[1].Value;
var codepoint = int.Parse(hex, NumberStyles.HexNumber);
return new string((char)codepoint, 1);
};
var escapeSequencePattern = @"\\[Uu]([\dA-Fa-f]{4})";
var withoutEscapes = Regex.Replace(input, escapeSequencePattern, replacer, RegexOptions.CultureInvariant);
withoutEscapes.Dump();
// Now do the real check.
var isIdentifier = @"^@?[\p{L}\p{Nl}_][\p{Cf}\p{L}\p{Mc}\p{Mn}\p{Nd}\p{Nl}\p{Pc}]*$";
return Regex.IsMatch(withoutEscapes, isIdentifier, RegexOptions.CultureInvariant);
}
Run Code Online (Sandbox Code Playgroud)
提问者早已不在,但我觉得有必要回答实际问题:
string[] parts = input.Split();
return parts.Length == 2
&& IsValidIdentifier(parts[0])
&& IsValidIdentifier(parts[1]);
Run Code Online (Sandbox Code Playgroud)
ECMA 334 § 7.4.3;ECMA 335 § I.10;Unicode TR 15 附件 7
不是最好的,但这会起作用。演示在这里。
^@?[a-zA-Z_]\w*(\.@?[a-zA-Z_]\w*)*$
Run Code Online (Sandbox Code Playgroud)
请注意,
* 不允许将数字0-9作为第一个字符
*只@允许作为第一个字符,但不允许作为其他任何地方(编译器将删除)
*是允许的_
编辑
根据您的要求,下面的内容Regex会更有用,因为输入属性名称不需要包含@在其中。检查这里。
^[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*$
Run Code Online (Sandbox Code Playgroud)