问题描述
在以下代码中,如果取消注释“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;
}
}
}
输出,未注释/已注释:
解决方法
找到:
这是带有 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>)
这是带有 using static
未注释的 IL code generated(所以 带有 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>,!!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 code generated)。
I'll say that namespace resolution works by level... 首先,C# 会尝试定义在 using
最内层的所有 namespace
,如果没有一个足够好,那么它会上升到 {{1} 的级别}.如果有多个候选人在同一级别,则选择最佳匹配。在没有 namespace
的示例和我给出的示例中,using static
+ using
都是顶级的,只有一个级别,因此 C# 选择了最佳候选者。在两级using static
中检查最里面的一层,而using
足以解析using static Enumerable
方法,所以没有做额外的检查。
这一次我要再说一遍,SharpLab 是这次响应的 MVP。如果您对 C# 编译器在幕后做了什么有疑问,SharpLab 可以给您答复(从技术上讲,您可以使用 ildasm.exe 或 ILSpy,但是 SharpLab 非常直接,因为它是一个网站,您可以交互式更改源代码)。 SVP(第二个有价值的球员)(对我来说)是 WinMerge,我用来比较 IL 程序集?
回复评论
using_namespace_directive 引用的 namespace_name 的解析方式与 using_alias_directive 引用的 namespace_or_type_name 的解析方式相同。因此,同一编译单元或命名空间体中的 using_namespace_directives 互不影响,可以按任意顺序编写。
然后
在使用命名空间指令中讨论了多个 using_namespace_directives 和 using_static_directives 之间的歧义。
所以第一条规则甚至适用于OrderBy
。这就解释了为什么第三个例子(我的)等价于 no-using static
。
关于为什么 using static
比 ParallelEnumerable.OrderedBy()
都被 C# 编译器检查时更好,很简单:
Enumerable.OrderBy()
返回一个 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,TKey> keySelector)
,它与 ParallelQuery<TSource>
返回的类型完全相同,不需要“向下转换”。