“?”类型修改器优先级与逻辑和运算符(&)与地址 – 运算符(&)

更新:似乎我不清楚我究竟要问的是什么(随着时间的推移,我也失去了一些问题),所以这里是一个tl; dr版本:

var test1 = a is byte & b; // compiles var test2 = a is byte? & b; // does not compile var test3 = a is byte? && b; // compiles 

这意味着 – 据我所知 – 那个? 类型修饰符具有较低的优先级 (因为它不是运算符,这可能不是最好的单词)而不是&运算符,但高于&&运算符。 是这样吗? 标准中描述了哪里?


原来的问题是:

在试图从Jon Skeet的优秀博客文章“两个谜题的故事”中找出第二个谜题的答案时,我遇到了一个问题:

 unsafe private void Test(T a, bool b) { var test1 = a is byte? & b; // does not compile var test2 = a is byte? && b; // compiles var test3 = a is byte ? & b : & b; // compiles } 

在这里,我使用unsafe上下文,因为我的实际目标需要它(例如:第三行),但没有必要再现我提出的问题。 (但它可能会产生影响,因为它引入了address-of运算符作为&符号的替代。)

第一行不编译(其他人都这样做),它给出以下错误消息:

 Syntax error, ':' expected 

这意味着在这种情况下,编译器将该行视为

 var test1 = (a is byte) ? &b [: missing part that it complains about]; 

在第二行中,它将其视为:

 var test2 = (a is byte?) && (b); 

我检查了运算符优先级( 这里 ),顺序(从最高到最低)是以下: &, &&, ?: ,所以这并不能解释为什么第一行不编译而第二行不编译(或者至少不是对我来说 – 也许这就是我错的地方……) 编辑:我理解为什么第二次编译,所以请不要在你的答案中专注于此。

我的下一个预感是,某种程度上优先 (如果有这样的事情) ? 类型修饰符介于这两个(或实际上是三个)运算符( &&& )之间。 可以这样吗? 如果没有,请有人解释我遇到的确切行为? 评估顺序是? 类型修饰符在标准的某处清楚地描述了吗?

编辑:我也知道有一个一元的地址操作符(实际上这是我试图用于解决方案的技巧……),它在这里起作用,但我的问题仍然保持不变。

在同样unsafe环境中,那些人愉快地编译:

 var test1 = true & b; var test2 = true && b; // or var test1 = a is byte & b; var test2 = a is byte && b; 

所以我认为它必须与?有关? 修饰符/运算符,而不仅仅是优先级的运算符地址(否则两个test1行不会编译)。

PS:我知道我可以在我的代码中添加括号,因此它会编译,但我想避免这样做:

 var test = (a is byte?) & b; // compiles 

更新:我稍微尝试了Roslyn,我认为为各种语句附加AST可能是个好主意:

 var test1 = a is byte & b; 

测试1行的AST

 var test2 = a is byte? & b; 

测试2行的AST

 var test3 = a is byte? && b; 

测试3行的AST


我想强调一点,我不是在寻找链接文章中的原始问题的解决方案(当然我正在寻找一个,但我请你不要在这里给出答案,因为我想找到它我自己。)另外,请不要评论我是否在找到解决方案的完全错误的轨道上,我只提到了为我的具体问题提供一些背景的谜题,并提供了一个足够好的借口来编写像这个。

这里的线索是unsafe关键字。 实际上有两个不同的&运算符 – 你习惯的按位AND运算符,还有地址运算符,它不仅具有更高的优先级,而且像所有一元运算符一样从右到左进行求值 。

这意味着首先评估&b ,并产生指针值。 正如编译器抱怨的那样,声明的其余部分是不可解析的。 它是(a is byte?) (address) ,还是(当编译器试图解析它时) (a is byte) ? (address) (a is byte) ? (address)并且错过了:

替换&时使用+-得到相同的编译错误,这两个符号可以是一元或二元运算符。

第二个语句编译好的原因是没有一元一对,从右到左的高优先级&&运算符。

说实话,我不太确定我是否应该将此作为答案发布或将此信息添加到 – 已经相当冗长 – 的问题,但我终于找到了为什么它的行为方式。 (但我仍然认为它没有在标准中明确描述,并且它实际上是编译器当前实现的限制。)

此外,我暂时不会接受我自己的答案,希望有人能够提供更好的答案。

我花了一点时间在Roslyn上 ,我通过lexing和解析这段代码中的各种语句来调试:

 var test1 = a is byte & b; var test2 = a is byte? & b; var test3 = a is byte? && b; 

确切的语法树已经添加到问题中,所以我不打算在这里重复它们。

这些语句之间的区别来自编译过程的这一部分(来自LanguageParser.cs ):

 private TypeSyntax ParseTypeCore( bool parentIsParameter, bool isOrAs, bool expectSizes, bool isArrayCreation) { var type = this.ParseUnderlyingType(parentIsParameter); if (this.CurrentToken.Kind == SyntaxKind.QuestionToken) { var resetPoint = this.GetResetPoint(); try { var question = this.EatToken(); // Comment added by me // This is where the difference occurs // (as for '&' the IsAnyUnaryExpression() returns true) if (isOrAs && (IsTerm() || IsPredefinedType(this.CurrentToken.Kind) || SyntaxFacts.IsAnyUnaryExpression(this.CurrentToken.Kind))) { this.Reset(ref resetPoint); Debug.Assert(type != null); return type; } question = CheckFeatureAvailability(question, MessageID.IDS_FeatureNullable); type = syntaxFactory.NullableType(type, question); } finally { this.Release(ref resetPoint); } } // Check for pointer types (only if pType is NOT an array type) type = this.ParsePointerTypeMods(type); // Now check for arrays. if (this.IsPossibleRankAndDimensionSpecifier()) { var ranks = this.pool.Allocate(); try { while (this.IsPossibleRankAndDimensionSpecifier()) { bool unused; var rank = this.ParseArrayRankSpecifier(isArrayCreation, expectSizes, out unused); ranks.Add(rank); expectSizes = false; } type = syntaxFactory.ArrayType(type, ranks); } finally { this.pool.Free(ranks); } } Debug.Assert(type != null); return type; } 

如果符号在byte?后面会出现相同的结果byte? 除了SyntaxKind.None ,其函数返回的部分:

 public static SyntaxKind GetPrefixUnaryExpression(SyntaxKind token) { switch (token) { case SyntaxKind.PlusToken: return SyntaxKind.UnaryPlusExpression; case SyntaxKind.MinusToken: return SyntaxKind.UnaryMinusExpression; case SyntaxKind.TildeToken: return SyntaxKind.BitwiseNotExpression; case SyntaxKind.ExclamationToken: return SyntaxKind.LogicalNotExpression; case SyntaxKind.PlusPlusToken: return SyntaxKind.PreIncrementExpression; case SyntaxKind.MinusMinusToken: return SyntaxKind.PreDecrementExpression; case SyntaxKind.AmpersandToken: return SyntaxKind.AddressOfExpression; case SyntaxKind.AsteriskToken: return SyntaxKind.PointerIndirectionExpression; default: return SyntaxKind.None; } } 

所以问题是在一个is (或一个)运算符之后,当我们面对一个? 令牌,然后我们检查下一个令牌是否可以解释为一元运算符,如果是这样的话:我们不关心的可能性是? 如果token是一个类型修饰符,我们只需返回它之前的类型,并相应地解析其余类型(有更多的条件要满足,但这是关于我的问题的相关信息)。 具有讽刺意味的是, &符号甚至不能是一元运算符,只是在不安全的环境中,但这从未被考虑过。

正如其他人在评论中指出的那样,如果我们向前看一点 ,也许可以解决这个问题,例如:在那种特殊情况下,我们可以检查是否存在匹配:对于? 令牌,如果不是,则忽略了一元&运算符的可能性并对待? 作为类型修饰符。 如果我有时间,我会尝试实施一个解决方法,看看它会导致更大的问题:)(幸运的是,Roslyn解决方案中有很多测试…)

感谢大家的反馈。