“使用静态”杀死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.exe或ILSpy,但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.