Luk*_*uel 1 c# linq linqpad roslyn
我正在研究IQueryable的实现; 但是,在我深入研究之前,我想确保我完全理解我需要评估的表达式树是什么样的.特别是,我很好奇在编译过程中如何将LINQ查询语法转换为方法语法.
我正在使用LINQPad来查看编译器生成的方法.我注意到在嵌套迭代中生成一个临时变量名来存储上层迭代的状态.这是一个例子:
from Event in EventQueue
from Ack in Event.Acknowledgements
where Ack.User == User.Name
select Event
Run Code Online (Sandbox Code Playgroud)
这相当于:
EventQueue
.SelectMany(
Event => Event.Acknowledgements,
(Event, Ack) =>
new
{
Event = Event,
Ack = Ack
}
)
.Where(temp0 => (temp0.Ack.User == User.Name))
.Select(temp0 => temp0.Event)
Run Code Online (Sandbox Code Playgroud)
当然,我的第一直觉是试图打破这一点,看看发生了什么.所以我写了以下查询:
from Event in EventQueue
from Ack in Event.Acknowledgements
let temp0 = Ack.User
where Ack.User == temp0
select Event
Run Code Online (Sandbox Code Playgroud)
这几乎是"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)
Run Code Online (Sandbox Code Playgroud)
这让我得出结论,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)
Run Code Online (Sandbox Code Playgroud)
现在,这样做有两个地方我必须为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;
}
Run Code Online (Sandbox Code Playgroud)
这个IL会是这样的:
ldc.i4.2
ldc.i4.3
mul
ldc.i4.2
add
ldc.i4.2
sub
ret
Run Code Online (Sandbox Code Playgroud)
请注意,没有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选项卡.