我正在研究 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)如何处理与生成代码的命名冲突?
最佳答案
This has led me to the conclusion that LINQPad is not pulling these method chains from the compiler.
正是因为它从编译器所做的事情中提取出来,您才会看到这一点。
您获取了一些 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
无论如何这是一个坏名字,如果我参与构建这个工具,那么修复它就不是我的高优先级。
How does the C# compiler (Roslyn, in this case) handle naming conflicts with generated code?
两种方式:
- 不需要。许多 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 选项卡。
关于c# - LINQ 是如何解决命名冲突的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46242501/