首先回答一个问题:is/as 关键字是通过CLR中的 isinst 指令实现,而非反射。
isinst 在当下CoreCLR实现中,对于引用以及值类型,JIT会生成代码调用CoreCLR内部的FCall方法 JIT_IsInstanceOfClass_PortableJIT_IsInstanceOfClass ,而对于接口类型的断定会调用JIT_IsInstanceOfInterface_PortableJIT_IsInstanceOfInterface 。
这里以 JIT_IsInstanceOfClass_Portable 为例:
HCIMPL2(Object*, JIT_IsInstanceOfClass_Portable, MethodTable* pTargetMT, Object* pObject) { FCALL_CONTRACT; //对传入对象引用判空 if (NULL == pObject) { return NULL; } //获取对象本身的MethodTable PTR_VOID pMT = pObject->GetMethodTable(); //遍历继承链,试图比较是否有相等的类型 do { if (pMT == pTargetMT) return pObject; pMT = MethodTable::GetParentMethodTableOrIndirection(pMT); } while (pMT); //判断是否有类型等效 if (!pObject->GetMethodTable()->HasTypeEquivalence()) { return NULL; } ENDFORBIDGC(); return HCCALL2(JITutil_IsInstanceOfAny, CORINFO_CLASS_HANDLE(pTargetMT), pObject); }
可以看到整个流程相当简单,核心就是获取对象的MethodTable在继承链上与目标MethodTable比较。一般来说只要继承链不是很长,都不会存在过多的Overhead。
当然其中另一个因素是FCall本身就是为托管代码对Runtime内部高性能方法调用所设计的:
综合上面两点,对 isinst 指令JIT生成的不过仅是简单的参数传递与一个call而已,Overhead不成大问题。
附:对代码
JIT为 isinst 指令生成的本机代码(x86)为
为了验证我们的猜想,进行实验对比:
对总是使用as
缓存as结果
10000次重复流程中,缓存as结果得到的时间消耗大概为3.4~3.7ms
而直接使用as不相上下,时间消耗大概为3.3~3.6ms
因此在最后我们可以得出结论,是否缓存as对运行时间的影响是无关痛痒的。
应评论要求用Stopwatch包裹住for循环,循环次数增加到一千万,总是使用as的时间消耗在40~50ms,而使用缓存策略则在100~110ms(带反转)就不放图了,懒。
仅仅是没有新消息热度下降了而已。想喷的人即使系统成了也会换个角度喷并且否认或者无视自己之前的错误论断,并且以之前得到错误论断同样的方法论基于新的信息得出新的论断,这样又可以开开心心的喷一阵子。