首先,在这里使用了大量的Emit和反射,因为不喜欢直接写IL汇编,所以看起来有点累,但是IL的知识是必须的,否则很难看明白这些代码是在干什么。
其次,要理解为什么会这样,需要了解一些程序底层的知识。
最后,准确的说,这里的对象并不是一个真实的对象,仅仅是把某段内存认为是某个对象,一般的高级语言不允许这样的代码,所以只能用最基础的IL来做。
1
static void Foo()
2
{
3
AssemblyName name = new AssemblyName("a");
4
AssemblyBuilder a = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
5
ModuleBuilder m = a.DefineDynamicModule("test", false);
6
TypeBuilder tb = Bar(m);
7
Type t = tb.CreateType();
8
MethodInfo mi = t.GetMethod("test", BindingFlags.Public | BindingFlags.Static);
9
mi.Invoke(null, null);
10
}
11
12
static TypeBuilder Bar(ModuleBuilder m)
13
{
14
// 创建一个Foo类
15
TypeBuilder result = m.DefineType("Foo");
16
// 定义一个bar字段
17
FieldBuilder field = result.DefineField("bar", typeof(int), FieldAttributes.Assembly);
18
// Foo的实例构造器
19
ConstructorBuilder ctor = result.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
20
ILGenerator gen = ctor.GetILGenerator();
21
gen.Emit(OpCodes.Ldarg_0);
22
gen.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
23
gen.Emit(OpCodes.Ldarg_0);
24
gen.Emit(OpCodes.Ldc_I4_0);
25
gen.Emit(OpCodes.Stfld, field);
26
gen.Emit(OpCodes.Ret);
27
28
// 定义test方法
29
MethodBuilder method = result.DefineMethod("test",
30
MethodAttributes.Public | MethodAttributes.Static,
31
CallingConventions.Standard, typeof(void), Type.EmptyTypes);
32
33
gen = method.GetILGenerator();
34
// 定义本地变量1、2、3
35
LocalBuilder local1 = gen.DeclareLocal(typeof(int));
36
LocalBuilder local2 = gen.DeclareLocal(typeof(int));
37
LocalBuilder local3 = gen.DeclareLocal(typeof(int));
38
39
// 用local2的地址作为对象地址,调用实例构造器
40
gen.Emit(OpCodes.Ldloca, local2);
41
gen.Emit(OpCodes.Call, ctor);
42
// 用local2的地址作为对象地址,把数字123放到字段里面
43
gen.Emit(OpCodes.Ldloca, local2);
44
gen.Emit(OpCodes.Ldc_I4, 123);
45
gen.Emit(OpCodes.Stfld, field);
46
47
// 来看看local1里面是什么
48
gen.Emit(OpCodes.Ldstr, "local1 address=");
49
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
50
gen.Emit(OpCodes.Ldloca, local1);
51
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
52
gen.Emit(OpCodes.Ldstr, "local1=");
53
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
54
gen.Emit(OpCodes.Ldloc, local1);
55
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
56
// 来看看local2里面是什么
57
gen.Emit(OpCodes.Ldstr, "local2 address=");
58
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
59
gen.Emit(OpCodes.Ldloca, local2);
60
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
61
gen.Emit(OpCodes.Ldstr, "local2=");
62
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
63
gen.Emit(OpCodes.Ldloc, local2);
64
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
65
// 来看看local3里面是什么
66
gen.Emit(OpCodes.Ldstr, "local3 address=");
67
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
68
gen.Emit(OpCodes.Ldloca, local3);
69
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
70
gen.Emit(OpCodes.Ldstr, "local3=");
71
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
72
gen.Emit(OpCodes.Ldloc, local3);
73
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
74
75
// 用local2的地址作为对象地址,看看字段里面是什么
76
gen.Emit(OpCodes.Ldstr, "Field address=");
77
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
78
gen.Emit(OpCodes.Ldloca, local2);
79
gen.Emit(OpCodes.Ldflda, field);
80
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
81
gen.Emit(OpCodes.Ldstr, "Field =");
82
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("Write", new Type[] { typeof(string) }));
83
gen.Emit(OpCodes.Ldloca, local2);
84
gen.Emit(OpCodes.Ldfld, field);
85
gen.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) }));
86
87
gen.Emit(OpCodes.Ret);
88
89
return result;
90
}
91

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

乍一看,代码有些长,下面仔细分析一下:
1、Foo方法,作用是为Emit做准备,并且测试,调用"test"方法
2、Bar方法,Emit一个叫"Foo"的引用类型的类,带有一个"Bar"的字段,并且有一个实例构造器和一个"test"方法
3、在实例构造器中,对"Bar"字段赋初始值0
4、在"test"方法中,用local2的地址作为对象的地址,Call实例构造器(注意不能用newobj),这样的效果等于初始化对象的初始值(也就是说,Call实例构造器后,"Bar"字段的值就是构造器中赋的初始值)
5、再次用local2的地址作为对象的地址,给"Bar"字段赋数字123(注意,只有这里出现了123这个数字)
6、从控制台分别输出local1、local2、local3,以及用local2的地址作为对象的地址的"Bar"字段的内存地址和他们的值
代码的作用就是这些,看一下运行的结果:
local1 address=1240840
local1=123
local2 address=1240836
local2=0
local3 address=1240832
local3=0
Field address=1240840
Field =123
local1=123
local2 address=1240836
local2=0
local3 address=1240832
local3=0
Field address=1240840
Field =123
运行结果中,可以意外的发现,local1也是123,而且local1的地址和"Bar"字段的地址一致。
当然这个值并不固定,而且local1、local2、local3的地址并不一定是递减的,也有可能是递增的,或者其它情况(虽然还没遇到,但是也不排除有分散的可能性)。
在递减的情况下,local1与以local2的地址作为对象地址的"Bar"字段的地址是一致的,也就是说,以local2的地址作为对象地址的情况下,"Bar"字段并不是在堆上,而是在栈上。对local1的任何改动可以影响"Bar"字段,反过来,对"Bar"字段的任何改动也可以影响local1,因为本质上它们就是同一个地址。
同样,以local2的地址作为对象地址的这个虚假引用类型的"对象",也是存在于栈上的,是从local2地址开始的,一片隶属于栈的内存。
最后,需要注意的一点是,这个方法有种钻运行时漏洞的感觉,在x86 .net 2.0的环境上测试通过,不保证其它运行时也能正常运行。