【问题标题】:How does LINQ resolve naming conflicts?LINQ 如何解决命名冲突?
【发布时间】:2017-09-15 14:58:13
【问题描述】:

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

【问题讨论】:

    标签: c# linq roslyn linqpad


    【解决方案1】:

    这使我得出结论,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 参数命名。我在这里选择了xy。我们也可以使用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;
    }
    

    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 时做错了什么,但虽然它可能会检查与用户定义名称的冲突,但对于“给我”的一般任务来说这并不是一个坏事这从编译器所做的回到 C# 中”。如果你想要编译器真正做了什么,请使用 IL 选项卡。

    【讨论】:

      猜你喜欢
      • 2015-04-12
      • 1970-01-01
      • 2021-02-22
      • 2019-01-25
      • 2011-01-31
      • 1970-01-01
      • 1970-01-01
      • 2010-12-16
      • 2016-02-20
      相关资源
      最近更新 更多