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参数的名称。我在这里用x
和y
去。我们可以使用foo
和bar
或theUnbearableLightnessOfBeing
和forgettingWhatYouCameForTheMomentYouSetFootInAShop
或其他。
当您尝试将C#编译器的输出转换回C#并选择以temp0
,然后temp1
等为开头的命名方案时,您使用的工具也做了类似的工作。这很不幸,因为你有一些明确的叫做temp0
,它没有考虑到这种情况。真的,因为无论如何,temp0
是一个坏名字,如果我参与构建这个工具,那么解决这个问题并不是我的高优先级。
C#编译器(本例中为Roslyn)如何处理与生成的代码的命名冲突?
两种方式:
- 不需要到。很多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
注意,没有x
,y
或z
在那里。从IL返回到C#的某些内容将不得不在那里创建名称。
- 使用无效的C#名称。
如果需要在生成的IL中执行某个名称并且该名称不存在于源中,那么C#编译器将使用一个有效的名称作为.NET标识符,但不是有效的作为C#标识符。允许标识符的.NET规则比C#规则宽松得多。
因此,它可以使用参数的名称,如<>h__TransparentIdentifier0
,<>h__TransparentIdentifier1
这是不允许的C#变量名,但完全没问题由.NET规则一般等,并知道它只需要跟踪它自己创造的名称:由于这些名称在C#中无效,因此作者在C#中放置的内容不会有冲突。 (这也是如何做的yield
创建的枚举类型不会与您创建的任何类冲突,等等)。
再一次,从IL返回到C#的东西将不得不在这里组成新的名字,以尝试生成有效的C#。
您可能会抱怨该工具在使用temp0
时出错,但虽然它可能很好地检查与用户定义名称的冲突,但对于“给我这个一般任务从编译器做的事情回到C#中“。如果你想要编译器真正做的,使用IL选项卡。