"?" type modifer precedence vs logical和operator(&)vs address-of operator(&)

qqb*_*enq 11 c# operators

更新: 似乎我不清楚我究竟要问的是什么(随着时间的推移,我也失去了一些问题),所以这里是一个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
Run Code Online (Sandbox Code Playgroud)

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


原来的问题是:

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

unsafe private void Test<T>(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
}
Run Code Online (Sandbox Code Playgroud)

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

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

Syntax error, ':' expected
Run Code Online (Sandbox Code Playgroud)

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

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

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

var test2 = (a is byte?) && (b);
Run Code Online (Sandbox Code Playgroud)

我检查了运算符优先级(这里),顺序(从最高到最低)是以下:&, &&, ?:,所以这并不能解释为什么第一行不编译而第二行不编译(或者至少不适合我 - 也许这是在哪里我错了...)编辑:我理解为什么第二次编译,所以请不要在你的答案中集中精力.

我的下一个预感是,不知何故,类型修饰符的优先级(如果有这样的东西)?介于这两个(或实际上是三个)运算符(&&&)之间.它可以吗?如果没有,请有人解释我遇到的确切行为??类型修饰符的评估顺序是否在标准中明确描述?

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

在同样的unsafe背景下,那些愉快的编译:

var test1 = true & b;
var test2 = true && b;
// or
var test1 = a is byte & b;
var test2 = a is byte && b;
Run Code Online (Sandbox Code Playgroud)

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

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

var test = (a is byte?) & b;   // compiles
Run Code Online (Sandbox Code Playgroud)

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

var test1 = a is byte & b;
Run Code Online (Sandbox Code Playgroud)

测试1行的AST

var test2 = a is byte? & b;
Run Code Online (Sandbox Code Playgroud)

测试2行的AST

var test3 = a is byte? && b;
Run Code Online (Sandbox Code Playgroud)

测试3行的AST


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

Avn*_*tan 6

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

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

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

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


qqb*_*enq 2

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

另外,我暂时不会接受自己的答案,希望有人能够给出更好的答案。

我花了一点时间研究Roslyn,并通过词法分析和解析此代码中的各种语句进行了调试:

var test1 = a is byte & b;
var test2 = a is byte? & b;
var test3 = a is byte? && b;
Run Code Online (Sandbox Code Playgroud)

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

语句之间的差异来自于这部分编译过程(来自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<ArrayRankSpecifierSyntax>();
        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;
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

所以问题是,在is(或as)运算符之后,当我们面对一个?标记时,我们检查下一个标记是否可以解释为一元运算符,如果是的话:我们不关心该?标记是一个的可能性type 修饰符,我们只需返回它之前的类型,并相应地解析其余部分(还有更多条件需要满足,但这是有关我的问题的相关信息)。具有讽刺意味的是,该&符号甚至不能是一元运算符,只能在不安全的上下文中,但从未考虑到这一点。

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

感谢大家的反馈。