csharpstandard icon indicating copy to clipboard operation
csharpstandard copied to clipboard

LINQ: Select Clause

Open bernd5 opened this issue 3 years ago • 8 comments

In 11.18.3.6 Select clauses the spec says that

from c in customers.Where(c => c.City == "London")
select c

is translated to

(customers).Where(c => c.City == "London")

But the current reference implementation translates it to (ignoring delegate caching and extension method expansion):

customers.Where(c => c.City == "London").Select(c => c)

-> The Select call is not omitted

Only for queries like:

from c in customers
where c.City == "London"
select c

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.

bernd5 avatar Dec 22 '22 14:12 bernd5

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?

KalleOlaviNiemitalo avatar Dec 22 '22 15:12 KalleOlaviNiemitalo

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";
	}
}

bernd5 avatar Dec 22 '22 16:12 bernd5

@KalleOlaviNiemitalo I think the emitting of Select is correct in your sample because of: degenerate-query-expressions

bernd5 avatar Dec 22 '22 17:12 bernd5

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.

KalleOlaviNiemitalo avatar Dec 22 '22 17:12 KalleOlaviNiemitalo

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.

bernd5 avatar Dec 22 '22 17:12 bernd5

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")

bernd5 avatar Dec 22 '22 17:12 bernd5

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».

svick avatar Dec 22 '22 17:12 svick

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)

Better sample:

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)

bernd5 avatar Dec 22 '22 18:12 bernd5