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