12

显然,C# 和C++一样容易受到“>>”词法分析器困境的影响。

这段 C# 代码非常有效,它编译并运行得很好:

var List = new Dummy("List");
var Nullable = new Dummy("Nullable");
var Guid = new Dummy("Guid");

var x = List<Nullable<Guid>> 10;
var y =  List<Nullable<Guid>> .Equals(10,20);

您必须为上面的 Dummy 类重载 '<' 和 '>>' 运算符。

但是编译器设法猜测在“x”情况下的含义是使用 List、Nullable 和 Guid 局部变量。在“y”的情况下,它突然决定将它们视为知名类型的名称。

这是另一个示例的更详细描述:http: //mihailik.blogspot.co.uk/2012/05/nested-generics-c-can-be-stinky.html

问题是:C# 编译器如何将 'a<b<c>>' 解析为算术表达式或泛型类型/方法?

在成功之前,它肯定不会尝试对程序的文本进行多次“遍历”,或者是吗?这将需要无限的前瞻,而且也非常复杂。

4

2 回答 2

7

我已被定向到 C# 语言规范中的第 7.6.4.2 段:

http://download.microsoft.com/download/0/B/D/0BDA894F-2CCD-4C2C-B5A7-4EB1171962E5/CSharp%20Language%20Specification.htm

简单名称(第 7.6.2 节)和成员访问(第 7.6.4 节)的产生式可能会导致表达式语法中的歧义。

...

如果可以(在上下文中)将标记序列解析为以类型结尾的简单名称(第 7.6.2 节)、成员访问(第 7.6.4 节)或指针成员访问(第 18.5.2 节) -argument-list(第 4.4.1 节),检查紧跟在关闭 > 标记之后的标记。如果它是其中之一

( ) ] } : ; , . ? == != | ^

然后类型参数列表被保留为简单名称、成员访问或指针成员访问的一部分,并且丢弃标记序列的任何其他可能的解析。否则,类型参数列表不被认为是简单名称、成员访问或指针成员访问的一部分,即使没有其他可能的标记序列解析。请注意,在解析命名空间或类型名称(第 3.8 节)中的类型参数列表时,不会应用这些规则。

因此,当涉及到 type-argument-list 时,确实可能会出现模棱两可的情况,并且他们有一种廉价的方法来解决它,方法是提前查看一个标记。

这仍然是一个无限制的展望,因为在“>>”和后面的标记之间可能有 1 兆字节的评论,但至少规则或多或少是清楚的。最重要的是,不需要推测性的深度解析。

于 2012-05-16T19:12:59.187 回答
-2

编辑:我坚持没有歧义: 在你的例子中根本没有歧义。这永远不能被评估为List<Guid?>. 上下文(额外的 10 个)显示编译器如何解释它。

var x = List<Nullable<Guid>> 10;

编译器会编译这个吗?:

var x = List<Guid?> 10;

很明显它不会。所以我仍在寻找歧义。

OTOH,第二个表达式:

var y =  List<Nullable<Guid>> .Equals(10,20);

必须评估为 a List<Guid?>,因为您正在调用该.Equals方法。同样,这可以用任何其他方式解释。

根本没有悖论。编译器完美解析它。我仍然想知道哪个是悖论。

你犯了一个大错误。编译器解释整个表达式,并使用语言语法来理解它们。它不会像您正在做的那样查看代码片段,而不考虑表达式的其余部分。

这些表达式根据C# 语法进行解析。并且语法足够清晰,可以正确解释代码。即在

var x = List<Nullable<Guid>> 10;

很明显,10 是一个字面量。如果你跟进语法,你会发现: 10 是一个 * literal,所以它是 * primary-no-array-creation-expression,这是一个 * primary-expression,这是一个 * unary-expression,这是一个*乘法表达式,这是一个 *加法表达式如果您在 * >>的右侧寻找加法表达式,您会发现它必须是 * shift-expression ,因此 * >>的左侧必须被解释为 *加法表达式,依此类推。

如果您能够找到使用语法的不同方式并为相同的表达式获得不同的结果,那么,我将不得不同意您的观点,但让我不同意!

最后:

  • 对人类来说非常混乱
  • 对编译器来说绝对清晰明确

因为:

  • 我们人类识别模式,提取我们熟悉的整个文本的片段,List<Nullable<Guid>>像我们想要的那样喜欢和解释它们
  • 编译器不会像我们一样解释代码,而是采用熟悉的片段,例如List<Nullable<Guid>>. 他们获取整个表达式并将其与语言语法相匹配。
于 2012-05-16T01:20:17.147 回答