Como C # Compiler escolher SelectMany ao traduzir expressão LINQ?
-
05-07-2019 - |
Pergunta
Existem 4 assinaturas sobrecarregados para Enumerable.SelectMany. Para tornar mais simples, ignoramos as duas assinaturas com int argumento. Então temos 2 assinaturas para SelectMany:
public static IEnumerable<TResult> SelectMany<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TResult>> selector
)
public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TCollection>> collectionSelector,
Func<TSource, TCollection, TResult> resultSelector
)
A minha pergunta é: como C # compilador escolher SelectMany ao traduzir expressão LINQ para invocação de método de extensão
Basicamente, se houver vários na expressão LINQ, haverá SelectMany. Mas, parece que compilador C # só escolher a segunda assinatura. A primeira assinatura nunca é utilizado.
IEnumerable<int> en1 = Enumerable.Range(1, 3);
IEnumerable<double> en2 = new double[] { 1.0, 3.14 };
IEnumerable<string> en3 =
from i1 in en1
from i2 in en2
select (i1 * i2).ToString();
foreach (var i in en3)
{
Console.WriteLine(i);
}
Com a ajuda do refletor, eu posso ver aquela expressão LINQ se traduziu em
en1.SelectMany<int, double, string>(delegate (int i1) {
return en2;
}, delegate (int i1, double i2) {
double CS$0$0000 = i1 * i2return CS$0$0000.ToString();
})
O exemplo acima envolve 3 tipos. Assim, é razoável para selecionar a segunda assinatura SelectMany. No entanto, para seguir exemplo, apenas um tipo está envolvido, ele ainda seleciona a segunda assinatura.
IEnumerable<int> en4 =
from i1 in en1
from i2 in Enumerable.Range(0, i1)
select i2;
Ele é traduzido em:
en1.SelectMany<int, int, int>(delegate (int i1) {
return Enumerable.Range(0, i1);
}, delegate (int i1, int i2) {
return i2;
})
Então, eu não posso encontrar um caso que a expressão LINQ é traduzido para a primeira assinatura SelectMany. Existe tal caso?
Se a primeira assinatura SelectMany não é usado, então ele existe apenas porque é BIND de mônada na programação funcional?
Talvez a pergunta pode ser:? Por que temos 2 assinaturas de SelectMany
Graças.
Solução
De acordo com a C # Spec, o compilador não irá gerar uma chamada de sobrecarga para a primeira versão do SelectMany. A primeira versão do SelectMany é útil para aplainar uma lista de listas em uma única lista plana.
public IEnumerable<string> Example(IEnumerable<IEnumerable<string>> enumerable) {
return enumerable.SelectMany(x => x);
}
Não tem um forte equivalente em uma expressão de consulta.
Veja a Seção 7.15.2 da Linguagem C # Spec para mais informações.
Outras dicas
Por que temos 2 assinaturas de SelectMany?
Então, eu posso usar o primeiro no meu código.
var orders = Customers.SelectMany(c => c.Orders)