LINQ如何解决命名冲突?

时间:2017-09-15 14:58:13

标签: c# linq roslyn linqpad

我正在开发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”并返回所有事件;但是,我不明白它是如何工作的,因为我给出的方法链永远不会编译:

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)如何处理与生成的代码的命名冲突?

1 个答案:

答案 0 :(得分: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)

现在,这样做有两个地方我必须为lambda参数提出一个名字。我和x以及y一起去了。我们也可以选择foobartheUnbearableLightnessOfBeingforgettingWhatYouCameForTheMomentYouSetFootInAShop或其他任何内容。

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

  

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

两种方式:

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

    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#。
    2. 如果需要在生成的IL中执行名称并且源名称中没有该名称,则C#编译器使用的名称作为.NET标识符有效但不能作为C#有效标识符。允许标识符的.NET规则比C#规则宽松得多。

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

      同样,从IL返回到C#的东西必须在这里组成新名称,以尝试生成有效的C#。

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