【读书笔记】.NET本质论第四章-Programming with Type(Part One)
2009-07-30 10:40
399 查看
在上一章中主要探讨的是CTS中的类型,基本上是类型的“静态结构”,本章将主要涉及类型的运行时结构。你定义了一个类型,然后实例化它,那么它在内存中的布局到底是什么样子的呢?声明一个类型到底占多少内存?是分配在栈上还是堆上?这些都是本章需要讨论的话题。不过这一篇先说一些简单的问题。
一个类型的实例要么是一个对象,要么是一个值,这要看这个类型是如何定义的。一般来讲,自定义的类的实例都是对象,而所有直接或间接从System.ValueType类派生的类型却是值类型(在这里,请区分类与类型的区别)。
好,那我这样是不是可以定义一个值类型了呢:
嗯,这样竟然是不行的,C#编译器将会给出如下的错误(而且你发现没,在Vs里输入上面的代码时,输入ValueType的时候却没有智能感知,也没有代码着色):
'MyValueType' cannot derive from special class 'System.ValueType'
哦,原来把System.ValueType当做一个特殊类处理了。
既然不能从System.ValueType继承,那我们有什么办法定义值类型啊:
就这样,你的MyValueType就是一个值类型了,使用ILDasm看看:
原来这个struct也是用.class元数据描述的,它还继承了System.ValueType。不过这个MyValueType却已经加上了sealed,这样你就不能再从MyValueType派生了。还有没有其他方法定义值类型呢?有,那就是枚举:
再看看编译器为上面的代码做了些什么事情:
原来是定义了一个从System.Enum派生的类型啊,从System.Enum的代码我们可以看到,System.Enum是一个抽象类,但这个抽象类是从System.ValueType派生而来的。 所以枚举也是一个值类型。
值类型一定是分配在栈上么?
记得园子里也有相关的讨论。一般书上都讲值类型是分配在栈上的,而引用类型是分配在堆上的。不过这要看值类型的使用方式,如果值类型作为方法的局部变量或者方法的参数,那么值类型才分配在栈上,而如果值类型作为引用类型的字段,那么该值类型则分配在堆上:
那值类型里如果“有一个引用类型”,该引用类型也分配在栈上么?
实际上“有一个引用类型”这个说法本来就不正确,值类型里是指向这个引用类型实例的引用(指针),这个指针指向的是堆上的引用类型所在的内存区域。
一个类型的实例要么是一个对象,要么是一个值,这要看这个类型是如何定义的。一般来讲,自定义的类的实例都是对象,而所有直接或间接从System.ValueType类派生的类型却是值类型(在这里,请区分类与类型的区别)。
好,那我这样是不是可以定义一个值类型了呢:
public class MyValueType : ValueType
{
//some code
}
嗯,这样竟然是不行的,C#编译器将会给出如下的错误(而且你发现没,在Vs里输入上面的代码时,输入ValueType的时候却没有智能感知,也没有代码着色):
'MyValueType' cannot derive from special class 'System.ValueType'
哦,原来把System.ValueType当做一个特殊类处理了。
既然不能从System.ValueType继承,那我们有什么办法定义值类型啊:
public struct MyValueType
{
}
就这样,你的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枚举的三个成员: [Serializable, ComVisible(true)] public enum LayoutKind { //CLR自动控制内存布局 Auto = 3, //使用偏移量在代码中显式控制布局 Explicit = 2, //按照开发人员书写代码时的字段顺序控制 Sequential = 0 } 如果使用class定义一个类型,C#编译器默认会使用LayoutKind.Auto,而如果定义一个struct,C#编译器默认会使用LayoutKind.Sequential。因为.NET中的struct的存在实际上是为了与哪些非托管代码交互的,这个时候就必须知道你的内存布局是个啥样子的,如果你使用Auto这种方式,那布局就是由CLR自动控制,你不知道CLR到底是如何自动的,也就无法交互了。不过,如果你确定你定义的这个struct不会与非托管代码交互,你也可以使用如下这样的代码覆盖C#编译器的默认设置了: [StructLayout(LayoutKind.Sequential)] public class MyObject { } [StructLayout(LayoutKind.Auto)] public struct MyValueType { } 下面是对应的IL代码: .class public sequential ansi beforefieldinit MyObject extends [mscorlib]System.Object { } .class public auto ansi sealed beforefieldinit MyValueType extends [mscorlib]System.ValueType { } 现在换了个儿,class的使用sequential,而struct使用auto(注意,LayoutKind和StructLayout都来自System.Runtime.InteropServices命名空间,请添加对应的using指令)。 CLR还允许你使用LayoutKind.Explicit配合FieldOffsetAttribute显式的指定每个字段的偏移(不过一般请不要这样做)。代码如下: [StructLayout(LayoutKind.Explicit)] public struct MyValueType { [FieldOffset(0)] public int A; [FieldOffset(4)] public int B; } 关于这个的更多示例可以参见MSDN |
原来这个struct也是用.class元数据描述的,它还继承了System.ValueType。不过这个MyValueType却已经加上了sealed,这样你就不能再从MyValueType派生了。还有没有其他方法定义值类型呢?有,那就是枚举:
public enum Color
{
Red,
White,
Black
}
再看看编译器为上面的代码做了些什么事情:
.class public auto ansi sealed Color
extends [mscorlib]System.Enum
{
.field public static literal valuetype Color Black = int32(2)
.field public static literal valuetype Color Red = int32(0)
.field public specialname rtspecialname int32 value__
.field public static literal valuetype Color White = int32(1)
}
原来是定义了一个从System.Enum派生的类型啊,从System.Enum的代码我们可以看到,System.Enum是一个抽象类,但这个抽象类是从System.ValueType派生而来的。 所以枚举也是一个值类型。
值类型一定是分配在栈上么?
记得园子里也有相关的讨论。一般书上都讲值类型是分配在栈上的,而引用类型是分配在堆上的。不过这要看值类型的使用方式,如果值类型作为方法的局部变量或者方法的参数,那么值类型才分配在栈上,而如果值类型作为引用类型的字段,那么该值类型则分配在堆上:
public class MyObject
{
//作为引用类型的字段使用,该值类型会分配在堆上
private int _objectField;
//valueParameter作为方法的参数分配在栈上
public void Test(int valueParameter)
{
//作为方法的局部变量,分配在栈上
int localVar = 5;
}
}
那值类型里如果“有一个引用类型”,该引用类型也分配在栈上么?
实际上“有一个引用类型”这个说法本来就不正确,值类型里是指向这个引用类型实例的引用(指针),这个指针指向的是堆上的引用类型所在的内存区域。
相关文章推荐
- 【读书笔记】.NET本质论第四章-Programming with Type(Part Two)
- 【读书笔记】.NET本质论第四章-Programming with Type(Part Two)--转
- 【读书笔记】.NET本质论第二章-Components(Part One)
- 【读书笔记】.NET本质论第三章-Type Basics(Part 3)
- 【读书笔记】.NET本质论第三章-Type Basics(Part 1)
- 【读书笔记】.NET本质论第三章-Type Basics(Part 2)
- cvc-complex-type.2.4.a: Invalid content was found starting with element 'async-supported'. One of
- 《Pro Ogre 3D Programming》 读书笔记 之 第四章 开始使用OGRE
- CORBA Programming with TAO - 3.IDL Data Type(数据类型与Mapping)
- Java之POI读取Excel的Package should contain a content type part [M1.13]] with root cause异常问题解决
- CORBA Programming with TAO - 3.IDL Data Type(数据类型与Mapping)
- The art of computer programming Donald E. Knuth volumn one third edition读书笔记2-1
- Cannot create TypedQuery for query with more than one return using requested result type
- 《The C++ Programming Language》第四章读书笔记之忠告
- Programming With POSIX Threads 读书笔记(三)——流水线技术[转载]
- Programming With POSIX Threads 读书笔记(一)
- Modular Programming with JavaScript-Packt Publishing 2016(读书笔记)
- Effective Java 英文 第二版 读书笔记 Item 3:Enforce the singleton property with a private constructor or an enum type.
- 《Pro Ogre 3D Programming》 读书笔记 之 第四章 开始使用OGRE