Understanding Struct, 理解Struct
在.Net 中各种基本类型都是由struct定义,他与引用类型相有一些不同,有自己的特色,这篇BLOG由ECMA335中关于Value Type的定义开始
用Class定义的类型不一定都是引用类型,实际上,值类型就不是(例如:ValueType).用Class定义的ValueType包含了拆箱的值类型和关联的装箱类型. (ECMA335 Partition I, 8.9.7)
- 当调用ValueType上的非静态方法时, this 指针是指向一个托管实例的引用,他会调用关联的装箱类型的方法.
- 值类型本身不支持接口,但是他关联的装箱的类型会.
- 值类型不能继承
- 装箱类型的基类不应该包含任何字段.
- 与引用类型不一样,值类型在创建实例时不需要调用构造函数. 默认他会把所有的字段根据类型设置为null或者0.
关于Struct类型使用的一般规则:
1. 不能定义无参数默认构造函数
1: public struct Employee
2: {
3: public string Name;
4:
5: public Employee(string name)
6: {
7: this.Name = name;
8: }
9:
10: public Employee()
11: {
12: }
13:
14: public void Run()
15: {
16: Console.WriteLine("HI, I am {0}", Name);
17: }
18: }
错误信息: Structs cannot contain explicit parameterless constructors (Structs 不能包含显示的无参构造)
2. 不能初始化
1: public struct Employee
2: {
3: public string Name = "Hello";
4: }
错误信息: 'Roger.Testing.Employee.Name': cannot have instance field initializers in structs (Structs中不能包含有初始化后的字段)
3. Read-Only状态不能更改
1: static readonly Employee emp = new Employee("Roger");
2:
3: public static void Main()
4: {
5: emp.Name = "New Roger";
6:
7: Console.Read();
8: }
错误信息: Fields of static readonly field 'Roger.Testing.Testing.emp' cannot be assigned to (except in a static constructor or a variable initializer) readonly类型的Struct不能被赋值, 除非在静态构造函数或者变量初始化时可以修改
例子:
1: static readonly Employee emp = new Employee("Roger");
2:
3: public static void Main()
4: {
5:
6: Console.Read();
7: }
8:
9: static Testing()
10: {
11: emp.Name = "New Roger";
12: }
4. 不能继承
1: public struct Manager : Employee
2: {
3: public string Title;
4: }
错误信息: 类型'Employee' 在接口列表中但不是接口
5. Struct可以继承无数接口
1: public struct Manager : IComparable, ICloneable
2: {
3:
4: #region IComparable Members
5:
6: public int CompareTo(object obj)
7: {
8: throw new NotImplementedException();
9: }
10:
11: #endregion
12:
13: #region ICloneable Members
14:
15: public object Clone()
16: {
17: throw new NotImplementedException();
18: }
19:
20: #endregion
21: }
6. StructLayout
详细解释: http://www.cnblogs.com/happyhippy/articles/717028.html
7. 序列化Struct
1: public static byte[]
2: RawSerialize(object anything)
3: {
4: int rawsize =
5: Marshal.SizeOf(anything);
6: IntPtr buffer =
7: Marshal.AllocHGlobal(rawsize);
8: Marshal.StructureToPtr(anything,
9: buffer, false);
10: byte[] rawdata = new byte[rawsize];
11: Marshal.Copy(buffer, rawdata,
12: 0, rawsize);
13: Marshal.FreeHGlobal(buffer);
14: return rawdata;
15: }
8. 调用API
详细解释: http://www.yesky.com/165/1621165.shtml
Struct类型上的方法调用
1: using System;
2: using System.Collections.Generic;
3:
4: namespace Roger.Testing
5: {
6: public class Testing
7: {
8: public static void Main()
9: {
10: Employee e = new Employee("www.xwang.org");
11: e.Run();
12:
13: Console.Read();
14: }
15: }
16:
17: public struct Employee
18: {
19: public string Name;
20:
21: public Employee(string name)
22: {
23: this.Name = name;
24: }
25:
26: public void Run()
27: {
28: Console.WriteLine("HI, I am {0}", Name);
29: }
30: }
31: }
编译IL
1: .method public hidebysig static void Main() cil managed
2: {
3: .entrypoint
4: // Code size 26 (0x1a)
5: .maxstack 2
6: .locals init (valuetype Roger.Testing.Employee V_0)
7: IL_0000: ldloca.s V_0
8: IL_0002: ldstr "www.xwang.org"
9: IL_0007: call instance void Roger.Testing.Employee::.ctor(string)
10: IL_000c: ldloca.s V_0
11: IL_000e: call instance void Roger.Testing.Employee::Run()
12: IL_0013: call int32 [mscorlib]System.Console::Read()
13: IL_0018: pop
14: IL_0019: ret
15: } // end of method Testing::Main
大家可以看到
IL_0007: call instance void Roger.Testing.Employee::.Run()
实际上就是调用了开头所说的第一条. :).
和朋友讨论,说难道是每一次方法调用就会产生一次装箱而后调用方法吗?
正如开文所言,Struct的this指针会指向在内存中的方法,将Struct作为第一个参数传给方法,而后执行 (自己没有去内存布局里面看,也没有确认了 :))。
int, double等都属于值类型范畴,所以本身调用方式一致
再看看WINDBG 中方法表的组成
方法表中包含了RUN方法的调用,进一步证实了一个Associated Boxed Type的存在.