LINQ如何解决命名冲突?

LINQ如何解决命名冲突?

问题描述:

我正在研究IQueryable的实现;但是,在我深入研究之前,我想确保我完全理解我需要评估的表达树会是什么样子。特别是,我对LINQ查询语法在编译过程中如何转换为方法语法感到好奇。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)如何处理与生成的代码的命名冲突?

这使我得出结论,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选项卡。