虽然工作也有几年时间了,但时常能暴露出基础知识薄弱的问题,因此,有空闲时间时总喜欢把缺下的功课补齐。
今天想讨论的是,接口到底是引用类型还是值类型。
要想说清楚接口到底是引用类型还是值类型,就需要先解释一下值类型跟引用类型。
值类型,简单理解就是继承自ValueType的类型,除过可空类型(如int?,bool?)外,值不能为null,通常情况下存储在栈中。而引用类型不从ValueType继承,其值可以为null,存储在托管堆中,由GC负责清理。
既然大致了解了什么是值类型和引用类型,或者说,值类型和引用类型的区别,那么有一个简单粗暴的办法判定接口类型问题----可以调用Object的实例方法GetType(),得到Type实例。通过Type的实例属性BaseType,直接判断接口的基类型。只是,接口可以理解成一种抽象类型,抽象类型是不允许实例化的,只能由实现类实例化。值类型以及引用类型都可以实现接口,所以通过基类型的方法并非个好方法。
偶然间发现一段代码:
const int i = 5;
IFormattable ftt = i;
这里不得不提到另一对概念,装箱和拆箱。装箱会将值类型经过一系列包装放到一个箱子,转换为引用类型,然后存放到托管堆上面。拆箱和这个过程相反,从托管堆上面拿到这个箱子,经过一些列的拆分操作,从引用类型转换成值类型。当然,拆箱过程涉及类型检查,这里不细说。总之,装箱可以简单理解为值类型-->引用类型,拆箱可以简单理解为引用类型-->值类型。有了这些理解就够了。
再来观察上面的代码段,i 为int类型,int类型是系统自定义的值类型。IFormattable是接口类型,如果接口类型是值类型,那么IFormattable ftt = i 是值类型到值类型的转换,将不存在装箱操作。如果接口类型是引用类型,那么IFormattable ftt = i 必然存在从值类型到引用类型的转换,即装箱操作。那就不多说了,采用IL查看工具看下源码就明白了!
using System; namespace Interview { class TestInterfaceType { public void Test() { const int i = 5; IFormattable ftt = i; } } }
Source Code
上面是源码,下面是IL源码(可以通过ILSpy、Reflactor或者VS 自带的ILDasm工具查看IL代码):
.method public hidebysig instance void Test () cil managed { // Method begins at RVA 0x2330 // Code size 9 (0x9) .maxstack 1 .locals init ( [0] class [mscorlib]System.IFormattable ftt ) IL_0000: nop IL_0001: ldc.i4.5 IL_0002: box [mscorlib]System.Int32 IL_0007: stloc.0 IL_0008: ret } // end of method TestInterfaceType::Test
IL Code
亮点在于 IL_0002: box [mscorlib]System.Int32 这一行,其中 box 就代表装箱指令。基本就可以断定,接口属于引用类型了。
当然,这些只是个人的一些理解,如果有不妥的地方,欢迎各位指正。