该值不一定会被装箱。C# 到 MSIL 的转换步骤通常不会进行大多数很酷的优化(出于一些原因,至少其中一些是非常好的),因此box
如果您查看 MSIL,您可能仍然会看到说明,但是如果 JIT 检测到它可以侥幸逃脱,它有时可以合法地忽略实际分配。从 .NET Fat 4.7.1 开始,开发人员似乎从未投资过教 JIT 如何弄清楚何时这是合法的。.NET Core 2.1 的 JIT 做到了这一点(不确定何时添加,我只知道它在 2.1 中有效)。
以下是我为证明这一点而运行的基准测试的结果:
BenchmarkDotNet=v0.10.14, OS=Windows 10.0.17134
Intel Core i7-6850K CPU 3.60GHz (Skylake), 1 CPU, 12 logical and 6 physical cores
Frequency=3515626 Hz, Resolution=284.4444 ns, Timer=TSC
.NET Core SDK=2.1.302
[Host] : .NET Core 2.1.2 (CoreCLR 4.6.26628.05, CoreFX 4.6.26629.01), 64bit RyuJIT
Clr : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3131.0
Core : .NET Core 2.1.2 (CoreCLR 4.6.26628.05, CoreFX 4.6.26629.01), 64bit RyuJIT
Method | Job | Runtime | Mean | Error | StdDev | Gen 0 | Allocated |
---------------------- |----- |-------- |---------:|----------:|----------:|-------:|----------:|
ViaExplicitCast | Clr | Clr | 5.139 us | 0.0116 us | 0.0109 us | 3.8071 | 24000 B |
ViaConstrainedGeneric | Clr | Clr | 2.635 us | 0.0034 us | 0.0028 us | - | 0 B |
ViaExplicitCast | Core | Core | 1.681 us | 0.0095 us | 0.0084 us | - | 0 B |
ViaConstrainedGeneric | Core | Core | 2.635 us | 0.0034 us | 0.0027 us | - | 0 B |
基准测试源代码:
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Exporters;
using BenchmarkDotNet.Attributes.Jobs;
using BenchmarkDotNet.Running;
[MemoryDiagnoser, ClrJob, CoreJob, MarkdownExporterAttribute.StackOverflow]
public class Program
{
public static void Main() => BenchmarkRunner.Run<Program>();
[Benchmark]
public int ViaExplicitCast()
{
int sum = 0;
for (int i = 0; i < 1000; i++)
{
sum += ((IValGetter)new ValGetter(i)).GetVal();
}
return sum;
}
[Benchmark]
public int ViaConstrainedGeneric()
{
int sum = 0;
for (int i = 0; i < 1000; i++)
{
sum += GetVal(new ValGetter(i));
}
return sum;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static int GetVal<T>(T val) where T : IValGetter => val.GetVal();
public interface IValGetter { int GetVal(); }
public struct ValGetter : IValGetter
{
public int _val;
public ValGetter(int val) => _val = val;
[MethodImpl(MethodImplOptions.NoInlining)]
int IValGetter.GetVal() => _val;
}
}