LINQ如何解决命名冲突?

Luk*_*uel 1 c# linq linqpad roslyn

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

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

from Event in EventQueue
from Ack in Event.Acknowledgements
where Ack.User == User.Name
select Event
Run Code Online (Sandbox Code Playgroud)

这相当于:

EventQueue
  .SelectMany(
    Event => Event.Acknowledgements,
    (Event, Ack) =>
      new
      {
        Event = Event,
        Ack = Ack
      }
  )
  .Where(temp0 => (temp0.Ack.User == User.Name))
  .Select(temp0 => temp0.Event)
Run Code Online (Sandbox Code Playgroud)

当然,我的第一直觉是试图打破这一点,看看发生了什么.所以我写了以下查询:

from Event in EventQueue
from Ack in Event.Acknowledgements
let temp0 = Ack.User
where Ack.User == temp0
select Event
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

Jon*_*nna 6

这使我得出结论,LINQPad没有从编译器中提取这些方法链.

正是因为它从你看到的编译器所做的事情中汲取了它.

您获取了一些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)
Run Code Online (Sandbox Code Playgroud)

现在,这样做有两个地方我必须为lambda参数提出一个名字.我x和他一起去了y.我们也可以同样有去foobartheUnbearableLightnessOfBeingforgettingWhatYouCameForTheMomentYouSetFootInAShop或什么的.

当您尝试将C#编译器的输出转换回C#并选择以temp0&然后temp1依此类推的命名方案时,您使用的工具执行了类似的工作.这是不幸的,因为你有一些明确调用的东西temp0,并没有考虑到这种情况.真的,temp0无论如何,因为我是一个坏名字,如果我参与构建这个工具,我不会高度优先解决这个问题.

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

两种方式:

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

考虑:

public int DoSum()
{
  int x = 2;
  int y = 3;
  int z = x * y + 2;
  return z - 2;
}
Run Code Online (Sandbox Code Playgroud)

这个IL会是这样的:

ldc.i4.2    
ldc.i4.3    
mul         
ldc.i4.2    
add         
ldc.i4.2    
sub         
ret
Run Code Online (Sandbox Code Playgroud)

请注意,没有x,yz在那里.从IL回到C#的东西将不得不在那里组成名称.

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

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

因此它可以使用参数名称<>h__TransparentIdentifier0,<>h__TransparentIdentifier1这些参数名称不允许作为C#变量名称,但一般情况下.NET规则完全可以等等,并且知道它只需跟踪自己创建的名称:因为这些名称不是在C#中有效,作者在C#中输入的内容不会发生冲突.(如果您yield创建的可枚举类型不会与您创建的任何类冲突,也是如此).

同样,从IL回到C#的东西将不得不在这里组成新名称,以尝试生成有效的C#.

您可能会抱怨该工具在使用temp0时出错了,但是对于它来检查与用户定义的名称的冲突可能是好的,对于"从C#中给我这个回复的一般任务来说,这不是一件坏事".编译器做了".如果您想要编译器真正做的事情,请使用IL选项卡.