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

时间:2014-06-19 11:18:14

标签: 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

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


原来的问题:

在试图从Jon Skeet的优秀博客A Tale of two puzzles中找出第二个谜题的答案时,我遇到了一个问题:

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
}

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

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

Syntax error, ':' expected

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

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

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

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

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

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

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

在相同的?上下文中,我们很乐意编译:

unsafe

所以我认为它必须与var test1 = true & b; var test2 = true && b; // or var test1 = a is byte & b; var test2 = a is byte && b; 修饰符/运算符相关,而不仅仅与优先级的运算符地址相关(否则两个?行将无法编译)。

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

test1

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

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

AST for test1 line

var test1 = a is byte & b;

AST for test2 line

var test2 = a is byte? & b;

AST for test3 line


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

2 个答案:

答案 0 :(得分:6)

这里的线索是unsafe关键字。实际上有两个不同的&amp;运算符 - 按位AND和&amp;您习惯的运营商,还有地址和&amp;运算符,它不仅具有更高的优先级,而且与所有一元运算符一样evaluated right-to-left

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

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

第二个声明编译好的原因是没有一元,从右到左的高优先级&amp;&amp;操作

答案 1 :(得分:2)

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

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

我花了一点时间在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<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;
}

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

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

感谢大家的反馈。