Open bernd5 opened 1 year ago
Microsoft (R) Visual C# Compiler version 4.8.4084.0 for C# 5 (C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe
in .NET Framework 4.8) likewise emits an Enumerable.Select call for the following source code:
using System;
using System.Collections.Generic;
using System.Linq;
static class C
{
static IEnumerable<int> M(IEnumerable<int> source)
{
return from i in source select i;
}
}
How about Mono's C# compiler?
I compiled the following code with mcs / mono (Mono C# compiler version 6.12.0.182). The result is similar to roslyn (Select is not omitted for Test1
but for Test2
):
Source:
using System;
using System.Linq;
using System.Collections.Generic;
class App{
public static void Main(){
Customer[] customers = new[]{
new Customer(){
}
};
Test1(customers);
Test2(customers);
}
static IEnumerable<Customer> Test1(IEnumerable<Customer> customers){
return from c in customers.Where(c => c.City == "London")
select c;
}
static IEnumerable<Customer> Test2(IEnumerable<Customer> customers){
return from c in customers
where c.City == "London"
select c;
}
}
class Customer{
public string City {get; set;}
}
Decompiled mcs / mono code (ILSpy):
// App
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
internal class App
{
[CompilerGenerated]
private static Func<Customer, bool> <>f__am$cache0;
[CompilerGenerated]
private static Func<Customer, Customer> <>f__am$cache1;
[CompilerGenerated]
private static Func<Customer, bool> <>f__am$cache2;
public static void Main()
{
Customer[] customers = new Customer[1]
{
new Customer()
};
Test1(customers);
Test2(customers);
}
private static IEnumerable<Customer> Test1(IEnumerable<Customer> customers)
{
if (<>f__am$cache0 == null)
{
<>f__am$cache0 = new Func<Customer, bool>(<Test1>m__0);
}
IEnumerable<Customer> source = Enumerable.Where(customers, <>f__am$cache0);
if (<>f__am$cache1 == null)
{
<>f__am$cache1 = new Func<Customer, Customer>(<Test1>m__1);
}
return Enumerable.Select(source, <>f__am$cache1);
}
private static IEnumerable<Customer> Test2(IEnumerable<Customer> customers)
{
if (<>f__am$cache2 == null)
{
<>f__am$cache2 = new Func<Customer, bool>(<Test2>m__2);
}
return Enumerable.Where(customers, <>f__am$cache2);
}
[CompilerGenerated]
private static bool <Test1>m__0(Customer c)
{
return c.City == "London";
}
[CompilerGenerated]
private static Customer <Test1>m__1(Customer c)
{
return c;
}
[CompilerGenerated]
private static bool <Test2>m__2(Customer c)
{
return c.City == "London";
}
}
@KalleOlaviNiemitalo
I think the emitting of Select
is correct in your sample because of: degenerate-query-expressions
Okay but then isn't this also a degenerate query expression?
from c in customers.Where(c => c.City == "London")
select c
In which case the standard contradicts itself.
That is my point.
That is not really a contradiction, but current compilers do not recognize the method-syntax in expression e
as part of the query...
This works only for pure query syntax.
Therefore I think just the sample needs to be corrected from:
from c in customers.Where(c => c.City == "London")
select c
is simply translated into
(customers).Where(c => c.City == "London")
To:
from c in customers
where c.City == "London"
select c
is simply translated into
(customers).Where(c => c.City == "London")
current compilers do not recognize the method-syntax in expression
e
as part of the query
Is there anything in the spec that says they should?
Therefore I think just the sample needs to be corrected [...]
The sample is technically correct in the context of what the spec says at the start of the section:
A query expression is processed by repeatedly applying the following translations until no further reductions are possible. The translations are listed in order of application: each section assumes that the translations in the preceding sections have been performed exhaustively, and once exhausted, a section will not later be revisited in the processing of the same query expression.
[...] needs to be corrected [...] to
The suggested code does not fit the pattern from «x» in «e» select «v»
.
Is there anything in the spec that says they should?
The sample which I refer to says that:
from c in customers.Where(c => c.City == "London")
select c
is translated to
(customers).Where(c => c.City == "London")
but it is translated to
(customers).Where(c => c.City == "London").Select(x => x)
from c in (
from x in customers
where x.City == "London"
select x
)
select c
The inner query:
from x in customers
where x.City == "London"
select x
is translated to:
from x in customers.Where(x=> x.City == "London")
select x
Then to:
customers.Where(x => x.City == "London")
So we get:
from c in customers.Where(x => x.City == "London")
select c
Which is further translated to:
customers.Where(x => x.City == "London").Select(x => x)
In 11.18.3.6 Select clauses the spec says that
is translated to
But the current reference implementation translates it to (ignoring delegate caching and extension method expansion):
-> The
Select
call is not omittedOnly for queries like:
the
Select
call is omitted.Is this a spec or roslyn bug?
To make it work, compilers would need to analyze invocation expressions and "expand" the original Linq query first - which is not done in roslyn.