需要了解的

 

在以前的一篇博文中,浅析了.net如何识别引用的真实类型

传送门 : http://blog.csdn.net/only_lonely/archive/2010/03/11/5370787.aspx

本篇算是其的续篇,主要讨论有关值类型的一些事情

在此,我假设你已经了解有关值类型与引用类型的一些基本事实,为了方便说明,我在此它们罗列上来

 

1,值类型的实例被分配在栈中,引用类型的实例被分配在堆中(GC堆,或者其他的)

 

2值类型只能继承于ValueType,引用类型能继承于任何类型(除了ValueType)

 

如果你明白上面两点,而且清晰无任何疑惑,OK,你可以看下去

 

1,从一段代码开始

 

using System;

namespace only_lonely
{
    class Demo
    {
        static void Main()
        {
            Int32 i = 10;
            i.ToString();
        }
    }
}

 

翻看它的IL代码

 

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       12 (0xc)
  .maxstack  1
  .locals init ([0] int32 i)
  IL_0000:  ldc.i4.s   10
  IL_0002:  stloc.0
  IL_0003:  ldloca.s   i
  IL_0005:  call       instance string [mscorlib]System.Int32::ToString()
  IL_000a:  pop
  IL_000b:  ret
} // end of method Demo::Main

 

嗯,很简单~ i.ToString() 在这里就类似 ToString(i),很简单的函数调用

 

接下来,改一下代码

 

using System;

namespace only_lonely
{
    class Demo
    {
        static void Main()
        {
            Int32 i = 10;
           i.GetType();
        }
    }
}

 

让我们DASM它

 

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       16 (0x10)
  .maxstack  1
  .locals init ([0] int32 i)
  IL_0000:  ldc.i4.s   10
  IL_0002:  stloc.0
  IL_0003:  ldloc.0
  IL_0004:  box        [mscorlib]System.Int32
  IL_0009:  call       instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
  IL_000e:  pop
  IL_000f:  ret
} // end of method Demo::Main

它装箱了,不是吗?

 

现在只有一个问题,why? 为什么调用 Int32 类型实例 的ToString() 仅仅会成为一个简单的函数调用,而调用 GetType() 却会造成装箱

让我们回顾一下装箱的概念 装箱,把一个值类型变成引用类型

值类型 变成 引用类型   嗯,事情变得有意思了哦

回顾我最前面链接的博文

引用 = sizeof(void*)大小的对象头+所有属于实例的字段

而那对象头中,又存有能够准确识别真实类型的信息

 

你有联想到什么吗?

比如,值类型是不支持类型安全的(准确识别对象的类型),想要获得对象的实际类型,必须装箱(转换成引用类型)

 

现在,是时候重新认识一下我们的值类型了

在.net中

 

1,所有的值类型都继承于ValueType,ValueType又继承于Object类型

2,所有的值类型都是密闭的,无法被继承

 

以上两句,你是否觉得很熟习?

:-),只要不是<< XXX从入门到精通 >>之类的书籍,或多或少的,都会提到上述两句话

 

可是,你是否会对上面的2句话感觉到困惑?为什么值类型一定要是那样? .net强制规定的? 没错,可是.net为什么会这样规定呢?

OK,现在,我来换一种方式,把上面的2句话解释给你听

所有的值类型都是密闭的,无法被继承   -> 这意味着,值类型没有子类 (1)

所有的值类型都继承于ValueType,ValueType是引用类型 –> 所有的值类型都有一个引用类型的父类 (2)

在OO中,很重要的一点就是可以把子类隐式的转换为父类 –> 这是条件 (3)

(1) + (2) + (3) –> 值类型没有子类,所以不存在把一个值类型(子类)隐式转换成另一个值类型(父类)的情况(A)

如果要对一个值类型进行隐式的转换,只能把值类型(子类)隐式转换成ValueType(父类,确定了的引用类型) (B)

.net的类型安全,很重要的一个部分就是可以动态的识别类型的实际类型,并对它们进行转换 –> 这是条件(C)

(A)+(B)+(C) –> 当值类型要进行有关类型转换的类型安全操作的时候~必须被转换成引用类型 (至此,请返回本文首部,看一下那些代码)

 

再一次地,回顾我的一篇博文 http://blog.csdn.net/only_lonely/archive/2010/03/11/5370787.aspx

.net依靠的是引用前 (sizeof(void*)) 大小的特殊对象头进行动态的类型识别

而值类型没有那些,综合起来,你是否有想到点什么?

 

C++里面的"值类型"

大牛们说过,不能随便用c++去解释c#,但无奈~我的实力实在有限,只能牵强的附会一下~因此,本文~只当作一个可能错误的参考与假设~理解大概的意思就行 :-)

  在c++中并没有所谓"值类型"的概念,但有基本类型的概念

  何谓基本类型?就是编译器足够,完全了解的类型,像如下代码

  int a;

  int b;

  int c = a+b; // 等价于 c = Add(a,b);

是有重载操作符,使得两个int类型的数字通过符号"+"进行"加法"的运算操作吗?

没错,的确是的~,只不过那个"函数",编译器替我们做了

 

  让我们做一个更为大胆的假设,假如编译器替我们做的更多… …

 

int a = 100;

a.ToString(); //等价于 ToString(a);

当编译器看到以上的代码,自动的调用一个形如String ToString(int);的函数,并把a当做参数传递,这是能做到的!

 

注意:因为值类型是密闭的,继承于引用类型的,所以当它重载一个父类的方法时,并不需要考虑多态性,即使ToString()是重载了父类的方法,也并不需要多余的考虑,直接一个Call 函数调用就行

 

编译器不能做的事情

 

编译器是静态的,一次运行就OK的,它并不能提供对类型的动态的运行时类型安全保障 -- 那是CLR做的事情,所以``当需要准确的知道值类型的类型时,需要把它转换成引用类型,并让CLR进行分析…. …