使用yield作为上下文关键字可能会引起问题

时间:2018-09-30 12:31:23

标签: c# language-design backwards-compatibility yield-return

Essential C#中指出:

  

在C#1.0之后,没有新的保留关键字引入C#。然而,   更高版本中的某些构造使用上下文关键字,这些关键字是   仅在特定位置有效。在这些指定之外   位置,上下文关键字没有特殊意义。*   方法,大多数C#1.0代码都与更高版本的标准兼容。

     

*例如,在C#2.0的早期设计中,语言设计师   指定收益作为关键字,并且Microsoft发布了alpha版本   以yield为指定关键字的C#2.0编译器   数以千计的开发人员。但是,语言设计师最终   确定通过使用收益率而非收益,他们可以   最终避免将yield作为关键字添加,因为它没有   在接近回报之外具有特殊意义。

现在我不明白这一点,就像在c#2.0之前一样,每个返回IEnumerable的方法都必须在其中具有return语句,而yield只能用作返回IEnumerable的方法中的上下文关键字,但是没有退货声明。例如

public IEnumerable<int> GetInts()
{
    for (int i = 0; i < 1000; i++)
        yield i;
}

由于该方法无法编译C#2.0之前的版本,因此我看不到如何破坏向后兼容性。

所以我的问题是:

在任何情况下,在C#中使用yield而不是yield return都会破坏向后兼容性,否则会引起问题?

2 个答案:

答案 0 :(得分:3)

问题

for (int i = 0; i < 1000; i++)
    yield i;

如果没有yield关键字,这确实是无效的,但是如果我们在i周围加上括号怎么办?

for (int i = 0; i < 1000; i++)
    yield (i);

现在,这是对名为yield的方法的完全有效的调用。因此,如果我们将yield (i);解释为使用上下文关键字yield,则此有效代码的含义将发生变化,从而破坏向后兼容性。

更正式的说法是这样的:如果我们更改C#2的语法,将statement: 'yield' 'return' expression ';'替换为statement: 'yield' expression ';',则该规则与该规则之间将存在歧义。函数调用的规则,因为expression可以派生到'(' expression ')',而'yield' '(' expression ')' ';'也可以是表达式语句中的函数调用。

可能的解决方案1 ​​

您当然可以说,只有yield i;(或任何其他不以括号开头的表达式)应被解释为使用上下文关键字,而yield (i);仍被视为方法调用。但是,这将是非常不一致和令人惊讶的行为-在表达式周围添加括号不应改变这样的语义。

这也意味着将上述语法规则更改为statement: 'yield' expressionNoStartingParen ';',然后定义expressionNoStartingParen,这将复制expression的大部分实际定义。这会使语法变得相当复杂(尽管您可以通过仅用单词而不是语法来描述无开始括号的要求来解决该问题,然后在实际实现中使用标记来对此进行跟踪(尽管可能不会使用大多数解析器生成器的选项))。

可能的解决方案2

解决此歧义的另一种方法(您已在注释中提到)将是在不具有return语句的非空方法内时,仅将yield expression;解释为yield语句。因为这样的方法无论如何在C#1中都是无效的,所以这将保持向后兼容。但是,这有点不一致,因为现在您可以定义一个名为yield的方法,并在不使用yield语句的方法中调用它,而在不使用yield语句的方法中调用它。

更重要的是,上下文关键字通常不是这样。通常,上下文关键字只要在标识符有效的任何地方使用,便会充当标识符,并且只能在不能出现标识符的地方用作关键字。这里不是这种情况。这不仅与上下文关键字通常的工作方式不一致,并使读者更难以区分关键字的收益率和标识符的收益率,也将使实现起来更加困难:

不仅无法仅通过查看那行来判断yield(x);是否为yield语句(您需要查看整个方法);解析器也不会-它必须知道该方法是否包含return语句。这将需要为有和没有语法返回的正文分别提供两个不同的定义,以及对每个标识符中允许使用的标识符的单独定义。要查看和实施,这将是一个可怕的语法。

在实践中,很可能会创建一个模棱两可的语法,然后将yield (x);解析为占位符AST,该占位符AST既包含yield语句又包含函数调用。然后,您将尝试对两者进行类型检查,并丢弃不进行类型检查的代码。这会起作用,但很少见,并且需要对解析器在编译器中的工作方式以及如何与AST一起工作进行大量更改。然后,该语言的任何其他实现(Mono,Roslyn)也不得不处理这种复杂性,从而使创建新实现更加困难。

结论

因此,总而言之,解决此问题的两种方法都导致一些不一致,并且后者也非常难以实现。与yield一起使用时,仅将return视为特殊字符可以避免歧义而不会造成任何不一致,并且易于实现。

答案 1 :(得分:0)

考虑得更多,我不认为即使在yield后面没有return的情况下,让yield成为上下文关键字也不会破坏向后兼容性。但是,这会带来一些奇怪之处。

例如

public class yield
{

}
...
public IEnumerable<yield> GetInts()
{
    yield item;
    item = new yield(); //compiler complains - item not declared.

    yield item2 = new yield(); //compiler complains-item2 not declared.
}

public int yield(int x) => 5;
public IEnumerable<int> GetInts()
{
    yield (5); //does not call the yield method as expected, but yields 5 instead
}

因此,尽管这些方法都无法在C#2.0之前的版本中进行编译,但使用yield return可以避免所有这些问题。