In the following code, if you uncomment the "using static" line, the query will not run in parallel. Why?
(Visual Studio Community 2019, .Net Core 3.1 / .Net 4.8)
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
namespace UsingStatic_Mystery
{
//using static Enumerable;
class Program
{
static void Main(string[] args)
{
var w = new Stopwatch();
iter:
w.Start();
var xx = Enumerable.Range(0, 10)
.AsParallel()
.OrderByDescending(x => {
Thread.Sleep(new Random().Next(100));
Console.WriteLine(x);
return x;
}).ToArray();
w.Stop();
Console.WriteLine();
foreach (var x in xx) Console.WriteLine(x);
Console.WriteLine(w.ElapsedMilliseconds);
Console.ReadLine();
w.Reset();
goto iter;
}
}
}
output, uncommented/commented:
Found:
This is the IL code generated with the using static
commented (so no using static
):
IL_0038: call class [System.Linq.Parallel]System.Linq.OrderedParallelQuery`1<!!0> [System.Linq.Parallel]System.Linq.ParallelEnumerable::OrderByDescending<int32, int32>(class [System.Linq.Parallel]System.Linq.ParallelQuery`1<!!0>, class [System.Private.CoreLib]System.Func`2<!!0, !!1>)
IL_003d: call !!0[] [System.Linq.Parallel]System.Linq.ParallelEnumerable::ToArray<int32>(class [System.Linq.Parallel]System.Linq.ParallelQuery`1<!!0>)
and this is the IL code generated with the using static
uncommented (so with using static
):
IL_0038: call class [System.Linq]System.Linq.IOrderedEnumerable`1<!!0> [System.Linq]System.Linq.Enumerable::OrderByDescending<int32, int32>(class [System.Private.CoreLib]System.Collections.Generic.IEnumerable`1<!!0>, class [System.Private.CoreLib]System.Func`2<!!0, !!1>)
IL_003d: call !!0[] [System.Linq]System.Linq.Enumerable::ToArray<int32>(class [System.Private.CoreLib]System.Collections.Generic.IEnumerable`1<!!0>)
The "correct" side is using Parallel.OrderBy
, the "wrong" side is using Enumerable.OrderBy
. The result you see is quite clearly for this reason. And the reason for why one or the other OrderBy
is selected is because with the using static Enumerable
you declare that the C# should prefer methods in the Enumerable
class.
More interestingly, had you written the using block like this:
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using static System.Linq.Enumerable;
namespace ConsoleApp1
{
so outside the namespace, everything would have worked "correctly" (IL code generated).
I'll say that namespace resolution works by level... First C# tries all the using
defined in the innermost level of namespace
, if there is no one that is good enough then it goes up a level of namespace
. If there are multiple candidates in the same level it takes the best match. In the example without the using static
and the example I gave where the using
+ the using static
are all top-level, there is a single level, so the C# takes the best candidate. In the two-levels using
the innermost one is checked, and the using static Enumerable
is good enough to resolve the OrderBy
method, so no extra checking is done.
I'll say that this time again, SharpLab was the MVP of this response. If you have a question about what the C# compiler does under the hood, SharpLab can give you the response (technically you could use ildasm.exe or ILSpy, but SharpLab is very immediate because it is a web site, and you can interactively change the source code). The SVP (second valuable player) (for me) was WinMerge, that I used to compare the IL assemblies 😀
Answer to the comment
The C# 6.0 draft reference page says
The namespace_name referenced by a using_namespace_directive is resolved in the same way as the namespace_or_type_name referenced by a using_alias_directive. Thus, using_namespace_directives in the same compilation unit or namespace body do not affect each other and can be written in any order.
and then
Ambiguities between multiple using_namespace_directives and using_static_directives are discussed in Using namespace directives.
so the first rule is applied even to using static
. This explains why the third example (mine) is equivalent to the no-using static
.
About why ParallelEnumerable.OrderedBy()
is better than Enumerable.OrderBy()
when both of them are checked by the C# compiler, it is simple:
The AsParallel()
returns a ParallelQuery<TSource>
(that implements IEnumerable<TSource>
)
The ParallelEnumerable.OrderedBy()
signature:
public static OrderedParallelQuery<TSource> OrderBy<TSource, TKey>(this ParallelQuery<TSource> source, Func<TSource, TKey> keySelector)
The Enumerable.OrderedBy()
signature:
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
The first one accepts a ParallelQuery<TSource>
, that is the exact same type returned by AsParallel()
, no "downcast" necessary.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With