问题
如果有这么一段代码
struct S
{
public int V1;
}
class A
{
public S SS{get;set;}
}
static void Main(string[] args)
{
var a = new A();
a.SS.V1 = 23333;
}
这时候VS就会提示:无法修改SS的返回值,因为不是变量。
可能到这里大部分人,都会知道 a.SS 这个其实在返回的实际是在stack 上的一个拷贝,于是在修改a.SS.V1的时候修改的是这个拷贝里的 V1 的值,而实际a这个示例中的值并没被修改。(关于C#中值类型复制拷贝,这里就先不说了)
如果到这里关于上面那个错误提示的原因算是知道了,但是怎么证明呢?
于是,这就引发下面的内容
证明
我们先来修改一下代码
unsafe class A
{
S s1;
public S SS
{
get
{
return s1;
}
set
{
s1 = value;
}
}
public IntPtr GetPsAddr()
{
fixed(int* p=&s1.V1)
{
return new IntPtr(p);
}
}
}
我们都知道 {get;set}
其实是一个语法糖,这次把这个语法糖展开成一个比较原始的写法,然后在A这个类内加一个方法,获取当前对象实例中V1的内存地址返回。
然后呢, Main这里这么小改一下:
static void Main(string[] args)
{
var a = new A();
var c=a.GetPsAddr().ToInt64();
long b =(long)&a.SS.V1;
Console.WriteLine($"c:{c:x}");
Console.WriteLine($"b:{b:x}");
}
于是乎,叮咚~ 编译器就又来报错了:
错误 CS1612 无法修改“Program.A.SS”的返回值,因为它不是变量
原因嘛当然是和上述的一样咯🙂
都到这里了,当然不能放弃,召唤出神器:
Dnspy
找到 /* 0x00000649 7C39000004 */ IL_001D: ldfld int32 TestThings.Program/S::V1
修改为 /* 0x00000649 7C39000004 */ IL_001D: ldflda int32 TestThings.Program/S::V1
ldfld 和 ldflda 不同在于 参考MSDN
ldfld: 查找对象中其引用当前位于计算堆栈的字段的值。
ldflda: 查找对象中其引用当前位于计算堆栈的字段的地址。
一顿操作,编译、运行,显示结果:
证明过程结束。