今天我遇到了一个幽灵方法,诡异至极,虽然我确信这个方法没有被调用过,无论是通过打断点,还是添加输出代码,我都在运行时验证了这一点,但诡异的是,那些只受该方法影响的变量,就是发生了改变。所以这是一个没有被调用,却又明显调用过的方法,一个幽灵方法。
这个方法大概是这样的:
public class Weight { ...... public virtual Weight setValue(double weight) { isSet = true; nextWeight1 = null; nextWeight2 = null; this.trueWeight = (float)weight; return this; } }
该方法会在值被设置的时候,做一些清理工作。
但出现幽灵现象的其实不是这个方法本身,问题发生在Weight的子类BiWeight身上。在我的逻辑中BiWeight的对象是没有机会调用到这个方法的,因此我也就没有重载这个方法在BiWeight中的实现。但最近我在调试中,需要查看BiWeight的内部变量的值的时候,却发现了一些诡异的现象。
调试时看到的内存状态
我检查了代码,确认唯有在这个方法中,isSet才有可能会被设为true,因此这个方法千真万确地被调用过。
为了确定这一点,我在BiWeight中重载了这个方法,并加了断点。
运行,断点没有被触发。我怀疑可能是被编译器优化掉了。
于是我添加了输出语句。
运行,没有相应的输出。
这么看来,这个方法应该没有被调用才对。
于是我有修改了实现,不再调用父类的方法。
运行,于是诡异的一幕发生了。
修改实现后对象的内存状态
内存状态确实变化了。
但是一个没有被调用的方法是如何影响到对象的内存状态的呢!!!
难道遇到鬼了?我写了这么多年的代码,撞见鬼打墙了?为什么会存在一个这样的幽灵方法呢?
在随后的一两个小时里,我对此进行了反复测试,测试结果仿佛指向了一个规律,对象的内存状态在告诉我,幽灵方法确实被调用了,而程序的外部输出又在告诉我另一个事实,幽灵方法并没有被调用。可是为什么呢?为什么程序会有内外两种表现?
身心俱疲的我陷入了绝境。
忽然,我注意到了内存状态中的一个展示项:TrueWeight。
TrueWeight是一个属性
TrueWeight是一个属性!!!
那一刻,我仿佛听见一个遥远的声音传来,真相只有一个!!!
在这个程序中,TrueWeight属性并不是对trueWeight的简单封装,他还有一些额外的逻辑:
public double TrueWeight { get { if (!isSet) { if (nextWeight1 != null) { setValue(nextWeight1.TrueWeight); } else if (nextWeight2 != null) { setValue(nextWeight2.TrueWeight); } setValue(infinitesimal); } return trueWeight; } }
我想,在C#编程实践中,在对属性的访问时,会执行一些附加的逻辑这种做法应该是相当普通的现象。而当我在查看对象的内存状态时,IDE同时也把对象附带的属性也显示出来。那么当IDE去访问属性时,会不会也触发了这些逻辑呢?
为了验证的我想法,我编写了一个简单的程序。
public class TestClass { public int v = 0; bool isSet = false; public int TestPropety { get { if (!isSet) { isSet = true; setValue(1); } return v; } } void setValue(int i) { Console.WriteLine($"setValu({i})"); v = i; } public static void Main() { TestClass test = new TestClass(); Console.WriteLine($"value = {test.v}"); } }
直接运行该程序的结果是这样的:
随后,我给程序打了断点:
运行,程序如预料之中的那样停在了Main方法体内的断点处,此时,我查看了一下test对象的内存状态:
setValue方法体内的断点没有触发,好像什么都没有发生一样。但v的值确实发生了变化。
我放开断点,继续运行,程序的输出如下:
作者:临岁之寒
链接:https://www.jianshu.com/p/cacc0cd700d8