来自MSDN。(更新:标题不准确,msdn指的pointer type是 “基础类型 *” 格式定义的变量类型,其中基础类型(referent type)可以是非托管类或者只包含非托管类型的自定义struct,因为pointer type不归GC管,所以不允许有reference type参与其中或者属于用户自定义struct。
下面的代码里的Node Struct就是满足条件的自定义struct,但是,另一方面,可以说是不相关的一点,我发现即使struct里含有string类型的成员和其他自定义struct, 直接试用时用reflector看到的仍然是根据地址来用的(也就是用ldloca之类的IL),也就是说它也是像pointer一样被使用的。没有用“基础类型 *“的格式就会被直接当作pointer type来用。从这个角度来说,标题又是对的。但是,这种情况下如果struct里含有string的reference,那个string能被GC吗?待验证。更新:string是immutable的特殊类,用weakReference.Alive来判断的话,GC.Collect()不会影响到它。用自己的类做实验,结果是可以被GC掉的。而这种含有managed成员的struct,就无法再用“基础类型 *“的格式来取得它的地址,它已经是作为managed Type的struct,:
Error 2 Cannot take the address of, get the size of, or declare a pointer to a managed type XXXXXX
所以,这种情况下的struct其实也不算可以作为point type。
这可真是c#和c++的一个大不同,这也解决了为什么用reflector看下列的代码(pointer和address操作必须用unsafe)
public struct Node
{
public int Data;
}
class TestPointer
{
public unsafe Node* getNode(int i)
{
Node n = new Node();
n.Data = i;
Node* ptr = &n;
return ptr;
}
会生成c#版本的
[StructLayout(LayoutKind.Sequential)]
public struct Node
{
public int Data;
}
public unsafe Node* getNode(int i)
{
Node n;
Node* ptr;
Node* CS$1$0000;
n = new Node();
&n.Data = i;
ptr = (IntPtr) &n;
CS$1$0000 = ptr;
Label_0019:
return CS$1$0000;
}
在n.Data = i;这句上,生成出的C#代码实际上是加了&(取地址)符号的。而如果你想在源代码里使用&n.Data=i,却不可以编译通过。所以,编译器在你访问struct的成员的时候会帮你标记出这是个指针访问(为什么不是变成n->Data呢,据说是reflector的C#语言反编译的语法bug?)看IL版本,实际还是指针访问
.method public hidebysig instance valuetype TestPointerForStruct.Node* getNode(int32 i) cil managed
{
.maxstack 2
.locals init (
[0] valuetype TestPointerForStruct.Node n,
[1] valuetype TestPointerForStruct.Node* ptr,
[2] valuetype TestPointerForStruct.Node* CS$1$0000)
L_0000: nop
L_0001: ldloca.s n
L_0003: initobj TestPointerForStruct.Node
L_0009: ldloca.s n //把变量n的地址压栈
L_000b: ldarg.1 //把第一个参数,也就是i,压栈
L_000c: stfld int32 TestPointerForStruct.Node::Data //成员赋值
L_0011: ldloca.s n
L_0013: conv.u
L_0014: stloc.1
L_0015: ldloc.1
L_0016: stloc.2
L_0017: br.s L_0019
L_0019: ldloc.2
L_001a: ret
}
对比类的赋值源代码:
m = new Myclass();
m.Name = "23";
生成的IL是:
L_003a: newobj instance void TestPointerForStruct.Myclass::.ctor()
L_003f: stloc.3
L_0040: ldloc.3 //把第三个变量,这里也就是m,压栈
L_0041: ldstr "23" //常量压栈
L_0046: stfld string TestPointerForStruct.Myclass::Name 。//成员赋值
所以指针类型(值类型)和引用类型的成员访问(偏移访问)都是一样的IL,只是压栈的时候一个是用ldloca.s var,压的是var的地址,另一个直接压第x个变量ldloc.x
注:上面两段IL的主要区别就是ldloc和ldloca的区别,也就是一个a,address的区别。