"using static" 杀死 AsParallel

"using static" kills AsParallel

在下面的代码中,如果取消注释“using static”行,查询将不会运行并行。为什么?

(Visual Studio 社区 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;
        }
    }
}

输出,uncommented/commented:

找到:

这是带有 using static 注释的 IL code generated(所以 没有 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>)

这是 IL code generatedusing static 未注释的(所以 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>)

“正确”的一方使用 Parallel.OrderBy,“错误”的一方使用 Enumerable.OrderBy。由于这个原因,您看到的结果非常清楚。选择其中一个 OrderBy 的原因是因为使用 using static Enumerable 您声明 C# 应该更喜欢 Enumerable class.[=52= 中的方法]

更有趣的是,你有没有像这样编写 using 块:

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;

using static System.Linq.Enumerable;

namespace ConsoleApp1
{

所以 命名空间之外,一切都会“正确”工作 (IL code generated)。

I'll say that namespace resolution works by level...首先C#尝试所有定义在最内层namespaceusing,如果没有一个足够好则上一层namespace。如果在同一级别 有多个候选人,则选择最佳匹配。在没有 using static 的示例和我给出的 using + using static 都是顶级的示例中,只有一个级别,因此 C# 是最佳候选者。在两层using中检查了最里面的那个,using static Enumerable足以解决OrderBy方法,所以没有做额外的检查。

我再说一遍,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,但是SharpLab非常直接,因为它是一个网站,你可以交互式地更改源代码). SVP(第二个有价值的球员)(对我来说)是 WinMerge,我用来比较 IL 程序集

回复评论

C# 6.0 draft reference 页面显示

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.

然后

Ambiguities between multiple using_namespace_directives and using_static_directives are discussed in Using namespace directives.

所以第一条规则甚至适用于 using static。这解释了为什么第三个例子(我的)等同于 no-using static.

为什么ParallelEnumerable.OrderedBy()Enumerable.OrderBy() 都被C# 编译器检查过,很简单:

AsParallel()returns一个ParallelQuery<TSource>(实现IEnumerable<TSource>

ParallelEnumerable.OrderedBy()签名:

public static OrderedParallelQuery<TSource> OrderBy<TSource, TKey>(this ParallelQuery<TSource> source, Func<TSource, TKey> keySelector)

Enumerable.OrderedBy()签名:

public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)

第一个接受 ParallelQuery<TSource>,与 AsParallel() 返回的类型完全相同,不需要“向下转换”。