最近我尝试使用泛型实现编译时调度(下面的例子)
public interface IAbstraction
{
public void Initialize();
}
public sealed class Implementation : IAbstraction
{
public void Initialize()
{
}
}
public sealed class GenericUsage<T> where T : class, IAbstraction
{
private readonly T _abstraction;
public GenericUsage(T abstraction)
{
_abstraction = abstraction;
}
public void CallAction()
{
_abstraction.Initialize();
}
}
public static void Main()
{
var genericUsage = new GenericUsage<Implementation>(new Implementation());
genericUsage.CallAction();
}
如您所见,我还明确使用了sealed
关键字作为信号,表示传递给构造函数的类型不可能与泛型中使用的类型不同。
在 JIT Asm 方面,我没有看到任何去虚拟化。调用仍然通过 vtable 进行。
有没有可能通过泛型实现编译时调度?也许我漏掉了什么。如果没有,为什么上面的示例不起作用?
有一个 2015 年未解决的问题与您尝试实现的问题非常相似: RyuJIT 调用优化和使用已知泛型类型的积极内联
来自评论(2018):
“共享版本”在共享泛型设计中有更好的解释
如果我们看一下反汇编(.NET 6 和 .NET 7,调试,x86),它是有道理的:
存在一种“规范的” jitted 方法,用于
GenericUsage<T>.CallAction()
每个T
。class
与身体:
JITter 无法去虚拟化并插入对 的直接调用(或内联),因为
Implementation.Initialize()
相同的 jitted 代码将被 “共享”GenericUsage<SecondImplementation>.CallAction()
。GenericUsage<ThirdImplementation>.CallAction()
正如原始 github 问题评论中所提到的,需要有“非共享引用类型”实例才能使其工作。
编辑:这个或其他东西是在 .NET 8 中实现的(如果有人评论确切的问题,我会进行编辑 - 但也许是动态 PGO?),当我们将泛型与类和结构进行比较时,这是可见的。结构通常总是会得到一个“非共享”实现,因此 JIT 可以更快地对其进行静态分派。稍微修改了一下代码以进行测试和基准测试:
T
a sealed class
.NET 6:
.NET 7
.NET 8(密封的 T 事项):