【读书笔记】.NET本质论第四章-Programming with Type(Part One)
2009-07-30 10:40 横刀天笑 阅读(1241) 评论(6) 编辑 收藏 举报在上一章中主要探讨的是CTS中的类型,基本上是类型的“静态结构”,本章将主要涉及类型的运行时结构。你定义了一个类型,然后实例化它,那么它在内存中的布局到底是什么样子的呢?声明一个类型到底占多少内存?是分配在栈上还是堆上?这些都是本章需要讨论的话题。不过这一篇先说一些简单的问题。
一个类型的实例要么是一个对象,要么是一个值,这要看这个类型是如何定义的。一般来讲,自定义的类的实例都是对象,而所有直接或间接从System.ValueType类派生的类型却是值类型(在这里,请区分类与类型的区别)。
好,那我这样是不是可以定义一个值类型了呢:
1: public class MyValueType : ValueType
2: {
3: //some code
4: }
嗯,这样竟然是不行的,C#编译器将会给出如下的错误(而且你发现没,在Vs里输入上面的代码时,输入ValueType的时候却没有智能感知,也没有代码着色):
'MyValueType' cannot derive from special class 'System.ValueType'
哦,原来把System.ValueType当做一个特殊类处理了。
既然不能从System.ValueType继承,那我们有什么办法定义值类型啊:
1: public struct MyValueType
2: {
3:
4: }
就这样,你的MyValueType就是一个值类型了,使用ILDasm看看:
.class public sequential ansi sealed beforefieldinit MyValueType extends [mscorlib]System.ValueType { }
延伸阅读
也许你比较了上面的IL代码与一个普通的用class定义的类型的区别,除了MyValueType从System.ValueType继承,而通常定义的一个class从System.Object外,我们还发现一个sequential元数据,而在class的对应位置应该是一个auto元数据。这是什么意思呢? 实际上这属于CLR控制类型中字段布局的问题,你写一个类型,在写代码的时候,字段的排列肯定是有顺序的,那么CLR如何安排这些字段呢?有三种布局模式,实际上也是LayoutKind枚举的三个成员: 1: [Serializable, ComVisible(true)] 2: public enum LayoutKind 3: {
4: //CLR自动控制内存布局 5: Auto = 3,
6: //使用偏移量在代码中显式控制布局 7: Explicit = 2,
8: //按照开发人员书写代码时的字段顺序控制 9: Sequential = 0
10: }
1: [StructLayout(LayoutKind.Sequential)]
2: public class MyObject 3: {
4:
5: }
6: [StructLayout(LayoutKind.Auto)]
7: public struct MyValueType 8: {
9: }
1: .class public sequential ansi beforefieldinit MyObject 2: extends [mscorlib]System.Object
3: {
4: }
5:
6: .class public auto ansi sealed beforefieldinit MyValueType 7: extends [mscorlib]System.ValueType
8: {
9: }
CLR还允许你使用LayoutKind.Explicit配合FieldOffsetAttribute显式的指定每个字段的偏移(不过一般请不要这样做)。代码如下: 1: [StructLayout(LayoutKind.Explicit)]
2: public struct MyValueType 3: {
4: [FieldOffset(0)]
5: public int A; 6:
7: [FieldOffset(4)]
8: public int B; 9: }
|
原来这个struct也是用.class元数据描述的,它还继承了System.ValueType。不过这个MyValueType却已经加上了sealed,这样你就不能再从MyValueType派生了。还有没有其他方法定义值类型呢?有,那就是枚举:
1: public enum Color
2: {
3: Red,
4: White,
5: Black
6: }
再看看编译器为上面的代码做了些什么事情:
1: .class public auto ansi sealed Color
2: extends [mscorlib]System.Enum
3: {
4: .field public static literal valuetype Color Black = int32(2)
5: .field public static literal valuetype Color Red = int32(0)
6: .field public specialname rtspecialname int32 value__
7: .field public static literal valuetype Color White = int32(1)
8: }
原来是定义了一个从System.Enum派生的类型啊,从System.Enum的代码我们可以看到,System.Enum是一个抽象类,但这个抽象类是从System.ValueType派生而来的。 所以枚举也是一个值类型。
值类型一定是分配在栈上么?
记得园子里也有相关的讨论。一般书上都讲值类型是分配在栈上的,而引用类型是分配在堆上的。不过这要看值类型的使用方式,如果值类型作为方法的局部变量或者方法的参数,那么值类型才分配在栈上,而如果值类型作为引用类型的字段,那么该值类型则分配在堆上:
1: public class MyObject
2: {
3: //作为引用类型的字段使用,该值类型会分配在堆上
4: private int _objectField;
5: //valueParameter作为方法的参数分配在栈上
6: public void Test(int valueParameter)
7: {
8: //作为方法的局部变量,分配在栈上
9: int localVar = 5;
10: }
11: }
那值类型里如果“有一个引用类型”,该引用类型也分配在栈上么?
实际上“有一个引用类型”这个说法本来就不正确,值类型里是指向这个引用类型实例的引用(指针),这个指针指向的是堆上的引用类型所在的内存区域。