LINQ 是如何解决命名冲突的?

How does LINQ resolve naming conflicts?

我正在研究 IQueryable 的实现;但是,在我深入研究之前,我想确保我完全理解我需要评估的表达式树是什么样子的。特别是,我很好奇 LINQ 查询语法在编译过程中是如何转换为方法语法的。

我正在使用 LINQPad 查看编译器生成的方法。我注意到在嵌套迭代中会生成一个临时变量名来存储上层迭代的状态。这是一个例子:

from Event in EventQueue
from Ack in Event.Acknowledgements
where Ack.User == User.Name
select Event

这相当于:

EventQueue
  .SelectMany(
    Event => Event.Acknowledgements,
    (Event, Ack) =>
      new
      {
        Event = Event,
        Ack = Ack
      }
  )
  .Where(temp0 => (temp0.Ack.User == User.Name))
  .Select(temp0 => temp0.Event)

当然,我的第一直觉是尝试打破它,看看会发生什么。所以我写了以下查询:

from Event in EventQueue
from Ack in Event.Acknowledgements
let temp0 = Ack.User
where Ack.User == temp0
select Event

这几乎是 "WHERE 1 = 1" 和 returns 的所有事件;但是,我不明白它是如何工作的,因为我得到的方法链永远不会编译:

EventQueue
  .SelectMany(
    Event => Event.Acknowledgements,
    (Event, Ack) =>
      new
      {
        Event = Event,
        Ack = Ack
      }
  )
  .Select(
    temp0 => 
      new
      {
        temp0 = temp0,
        temp0 = temp0.Ack.User  // Anonymous object with identically-named properties
      }
  )
  .Where(temp1 => (temp1.temp0.Ack.User == temp1.temp0))
  .Select(temp1 => temp1.temp0.Event)

这让我得出结论,LINQPad 没有从编译器中提取这些方法链,因为查询有效而这个方法链显然不会。 LINQPad 很可能会自行生成方法链。

C# 编译器(在本例中为 Roslyn)如何处理与生成代码的命名冲突?

This has led me to the conclusion that LINQPad is not pulling these method chains from the compiler.

正是因为它从编译器所做的事情中提取出来,所以你看到了这个。

您获取了一些 C# 代码,对其进行了编译,然后使用工具再次查看了该代码。

如果我们手动将其从查询语法 C# 代码转换为 C# 中的扩展方法调用,我们可能会想出如下内容:

EventQueue.SelectMany(
  Event => Event.Acknowledgements,
  (Event, Ack) => { Event = Event, Ack = Ack}
  )
  .Select(x => new { x = x, temp0 = x.Ack.User})
  .Where(y => (y.x.Ack.User == y.temp0))
  .Select(y => y.x.Event)

现在,在这样做的过程中,有两个地方我必须为 lambda 参数想出一个名字。我在这里选择 xy。我们也可以使用 foobartheUnbearableLightnessOfBeingforgettingWhatYouCameForTheMomentYouSetFootInAShop 或其他。

在尝试将 C# 编译器的输出转回 C# 并选择以 temp0 开头然后是 temp1 等的命名方案时,您使用的工具做了类似的工作.这很不幸,因为您有一些明确称为 temp0 的东西,但它没有说明这种情况。真的,因为 temp0 无论如何都是一个坏名字,如果我参与构建这个工具,那么修复它就不是我的优先事项。

How does the C# compiler (Roslyn, in this case) handle naming conflicts with generated code?

两种方式:

  1. 不需要。许多 C# 构造在生成的 IL 中根本没有任何名称。

考虑:

public int DoSum()
{
  int x = 2;
  int y = 3;
  int z = x * y + 2;
  return z - 2;
}

它的 IL 将类似于:

ldc.i4.2    
ldc.i4.3    
mul         
ldc.i4.2    
add         
ldc.i4.2    
sub         
ret

请注意,其中没有 xyz。从 IL 返回到 C# 的某些东西将不得不在那里弥补名称。

  1. 使用了无效的 C# 名称。

如果需要执行的操作在生成的 IL 中有一个名称,但该名称在源代码中不存在,则 C# 编译器将使用一个作为 .NET 标识符有效但无效的名称。一个 C# 标识符。允许的标识符的 .NET 规则比 C# 规则宽松得多。

因此它可以使用像 <>h__TransparentIdentifier0<>h__TransparentIdentifier1 这样的参数名称,这些名称不允许作为 C# 变量名称,但通常完全符合 .NET 规则等等,并且知道它需要仅跟踪其自己创建的名称:由于这些名称在 C# 中无效,因此作者在 C# 中输入的内容不会发生冲突。 (这也是如果你 yield 创建的可枚举类型不会与你创建的任何 类 冲突,等等)。

同样,从 IL 返回到 C# 的某些内容将必须在此处创建新名称,以尝试生成有效的 C#。

您可能会抱怨该工具在使用 temp0 时做错了什么,但是虽然它可以很好地检查与用户定义的名称是否发生冲突,但对于一般任务来说这并不是一件坏事"give me this back in C# from what the compiler did"。如果您想要编译器真正执行的操作,请使用 IL 选项卡。