“使用静态”杀死AsParallel

c#

在以下代码中,如果取消注释“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;
        }
    }
}

输出,未注释/注释:

回答

成立:

这是使用注释生成的IL 代码using static(所以没有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 代码using 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类中的方法。

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

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

using static System.Linq.Enumerable;

namespace ConsoleApp1
{

所以命名空间之外,一切都会“正确”地工作(生成 IL 代码)。

我会说,命名空间分辨率作品水平...首先C#尝试所有的using在最里面的级别定义namespace,如果没有一个是足够好的那么它上升的水平namespace。如果同一级别有多个候选人则选择最佳匹配。在没有的示例using static和我给出的示例中,using+using static都是顶级的,只有一个级别,因此 C# 选择了最佳候选者。两层using中检查最里面的一层,using static Enumerable已经足够解析OrderBy方法了,所以不做额外的检查。

我要再说一次,SharpLab是这次响应的 MVP。如果您对 C# 编译器在幕后做了什么有疑问,SharpLab可以给您答复(从技术上讲,您可以使用ildasm.exeILSpy,但SharpLab非常直接,因为它是一个网站,您可以交互地更改源代码)。SVP(第二个有价值的球员)(对我来说)是WinMerge,我用来比较 IL 程序集

回复评论

在C#6.0草案参考页说:

using_namespace_directive 引用的 namespace_name 的解析方式与 using_alias_directive 引用的 namespace_or_type_name 的解析方式相同。因此,同一编译单元或命名空间主体中的 using_namespace_directives 不会相互影响,并且可以按任意顺序编写。

进而

使用命名空间指令中讨论了多个 using_namespace_directives 和 using_static_directives 之间的歧义。

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

关于为什么ParallelEnumerable.OrderedBy()Enumerable.OrderBy()当它们都被 C# 编译器检查时更好,很简单:

AsParallel()回报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)

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

  • Is it fair to boil this down and say this is by design then? I see nothing in the [documentation](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods) about how the compiler picks extension methods where there's more than one that fits the bill. Your answer makes sense to me, just would love to see where in the C# spec or documentation this is made clear.

以上是“使用静态”杀死AsParallel的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>