作者:bartholomew 来源:博客园 酷勤网收集 2008-05-17
今天在调试同事的一段代码时,发现有一段代码调试时怎么也进不去,难道遇到了Visual Studio的调试禁区?
简化后的代码如下:
class Program
{
static void Main(string[] args)
{
SimpleClass simpleClass = new SimpleClass();
string myString = simpleClass.OneField;
}
}
public class SimpleClass
{
private string _oneField = string.Empty;
public string OneField
{
get
{
if (_oneField.Length == 0)
{
_oneField = GetMyString();
}
return _oneField;
}
}
private string GetMyString()
{
return "just for test~";
}
}无法调试的代码即“_oneField = GetMyString();”这一行。
大家可以自行调试,会发现是因为_oneField这个变量总是被莫名其妙的赋上值了,但明明我们还没有给它赋值……
和同事讨论过后,还是没有得出明确的结论,只是推测可能是因为Visual Studio在调试的过程中,自动扫描对象中的属性,然后自行运行属性的get操作~
不知道大家有没有遇到这种情况,虽然最终通过把OneField属性改写成函数解决了这个问题,但还是有些不爽,难道在Visual Studio就真的没有办法调试如上代码?
更新结论:
经过大家的反馈,我重复试了多次,总结如下,欢迎大家反馈意见——
要重现我以上的问题,可按如下步骤操作:在“SimpleClass simpleClass = new SimpleClass();”这一行加上断点,然后逐行调试,并且(请注意,这一点很重要,相信很多兄弟和我的调试结果不一样,可能就是因为这个原因),满足以下任一条件:
1、在调试过程中打开了Autos窗口
2、将simpleClass.OneField添加到了Watch中
3、运行到“simpleClass.SecondField = "me";”这一行时,鼠标移到对象“simpleClass”上,在弹出的信息显示层中点击“+”,查看一下“simpleClass”对象的各属性值,嗯,这个操作的作用和打开Autos窗口一样)
那么,最终是无法到达“_oneField = GetMyString();”这一行的;
进一步更新:
本以为以上发现的问题不会影响程序运行的结果,但却意外发现有时也会影响到程序最终运行的结果——
代码如下:
class Program
{
static void Main(string[] args)
{
SimpleClass simpleClass = new SimpleClass();
simpleClass.SecondField = "me";
string myString = simpleClass.OneField;
Console.WriteLine(myString);
Console.Read();
}
}
public class SimpleClass
{
private string _oneField = string.Empty;
private string _secondField = string.Empty;
public string OneField
{
get
{
if (_oneField.Length == 0)
{
_oneField = GetMyString();
}
return _oneField;
}
}
public string SecondField
{
get
{ return _secondField; }
set
{ _secondField = value; }
}
private string GetMyString()
{
return "SecondField:" + SecondField;
}
}以上这段代码正确的输出应该是:“SecondField:me”。
但如果我们在调试中这样做了的话。。。
嗯,还是从“SimpleClass simpleClass = new SimpleClass();”这一行开始逐行调试,运行到“simpleClass.SecondField = "me";”这一行时(注意,一定要在运行“simpleClass.SecondField = "me";”这一语句之前进行以下操作),鼠标移到对象“simpleClass”上,在弹出的信息显示层中点击“+”,查看一下“simpleClass”对象的各属性值,然后,OK,F5吧,你会发现,最后的输出变成了:“SecondField:”!!!
到目前为止最好的解决办法:
Options -> Debugging -> General -> Enabled property evaluation and other implicit function calls
去掉这个选项的选中状态!
然后,以上所有的问题都得以解决——这个世界,终于清静了。。。。。。


Feedback
#1楼 2008-05-15 22:54 Gray Zhang
我在VS2008中可以下断点啊……
#2楼 [楼主] 2008-05-15 22:57 bartholomew
你确定可以跟到_oneField = GetMyString();这一行?!
当然,在这一行打断点肯定是可以的,只是调试时到不了这样一行而已……
#3楼 2008-05-15 22:58 zeus2
证明.NET在创建类的时候把所有属性值缓存起来把Get过程都调用了一遍。
#4楼 2008-05-15 23:24 Angel Lucifer
俺在VS2008中也可以调试。
跟踪到楼主所指的那行没有问题。
#5楼 [楼主] 2008-05-15 23:29 bartholomew
Gray Zhang和Angel Lucifer 两位兄弟确定是用我文中的代码进行调试的吗?
我也是在vs2008中试的啊,但怎么也调不到_oneField = GetMyString();这一行!
#6楼 2008-05-15 23:31 Angel Lucifer
俺是直接Copy的。代码一字不差。
#7楼 [楼主] 2008-05-15 23:31 bartholomew
to zeus2:好像是这样,不然无法解释啊~
#8楼 [楼主] 2008-05-15 23:32 bartholomew
@Angel Lucifer
。。。
你运行到_oneField = GetMyString();这一行的上一行时,_oneField中还是没值?_oneField.Length == 0??
#9楼 [楼主] 2008-05-15 23:39 bartholomew
进一步发现问题了:
如果只在_oneField = GetMyString();这一行直接加断点,可以调试到这一行;
但如果在SimpleClass simpleClass = new SimpleClass();这一行加断点,然后逐行调试,那么,不管在_oneField = GetMyString();这一行是否加了断点,都无法调试到这一行
#10楼 2008-05-15 23:41 齐世昌
我在VS2005中调试也没有你说的问题啊
#11楼 2008-05-15 23:43 Angel Lucifer
@bartholomew
我也发现这个问题了。
#12楼 2008-05-15 23:47 Angel Lucifer
我靠,又重新Debug了一遍。
怎么走都会走到楼主说的那一行。
晕了。
#13楼 2008-05-15 23:52 Angel Lucifer
会不会跟CPU指令乱序执行有关系?
#14楼 [楼主] 2008-05-15 23:54 bartholomew
@Angel Lucifer
我也有点晕了,我试的结果:
在SimpleClass simpleClass = new SimpleClass();和_oneField = GetMyString();都加上断点的话,如果一路F10下去,到不了第二个断点,但如果一路F5下去,就可以到第二个断点。。。
#15楼 2008-05-16 00:02 无常
恭喜你,遇到个调试器的BUG~
#16楼 2008-05-16 00:05 Angel Lucifer
俺自从按了F11逐语句调试后,怎么样都会Debug到第二个断点了,呵呵。
这个才算正常吧。
#17楼 [楼主] 2008-05-16 00:09 bartholomew
@Angel Lucifer
不会吧,我一路按F11也到不了第二个断点啊~
#18楼 2008-05-16 00:14 Angel Lucifer
@bartholomew
晕。
看来只能寄希望于精通Debug的牛人来解答了。
#19楼 [楼主] 2008-05-16 00:35 bartholomew
@Angel Lucifer
我把文章改了下,希望迎来大牛的权威解释~
#20楼 2008-05-16 03:27 yuchen
调试器有超前意识 知道OneField不能为空就提前给值了 呵呵
#21楼 2008-05-16 04:31 Q.yuhen
可能是 IDE 捣的鬼,当 string myString = simpleClass.OneField; 被中断时,IDE 需要显示环境变量(locals,左下角),这其中就包括了 OneField 的值,因此 get_OneField 已经会被隐式执行。当你继续时,自然不会再次执行 _oneField = GetMyString(); 这行,也就不会触发断点了。
我以前碰到过类似情况。 http://www.rainsts.net/article.asp?id=45
#22楼 2008-05-16 07:31 wjx
你用windbg试试就知道了,不可能到不了。
#23楼 2008-05-16 08:31 Anders Liu
醒目:牛人已在21楼出现
#24楼 2008-05-16 08:59 allies
学习了~真是不看不知道啊 21楼的解释很详细了!
#25楼 2008-05-16 09:03 Allen Lee
我复制了你的第一份代码,把断点设在你所说的那一行,结果Visual Studio能正常在这里中断,并且可以继续单步进入GetMyString()方法,_oneField字段的值也正常。(我用的是Visual Studio 2008)
#26楼 2008-05-16 09:06 Allen Lee
至于第二个代码,我的输出结果是:
SecondField:me
#27楼 2008-05-16 09:15 hbb0b0
我的也正常着
#28楼 2008-05-16 09:16 球球
正常的,因为_oneField在你看属性值的时候已经被执行过了
#29楼 2008-05-16 09:18 bangbang
博主用的是vs2008吗?
#30楼 2008-05-16 09:23 BAsil
同意雨痕的,我也碰到过,是由于监视窗口可能赋值了该变量,导致代码不再执行,可以把监视窗口晴空。
#31楼 2008-05-16 09:28 bangbang
21果然大牛,发现debug可以去掉属性求值和隐式函数调用的,去掉后一切正常.
#32楼 [楼主] 2008-05-16 09:51 bartholomew
@Allen Lee
我把文章修改了一下,你可以再看一下我更新的部分~
第一份代码,你能够正常调试到那一行的原因,我猜是因为你在调试过程中,Autos窗口没有开启。。。
至于第二份代码,你的输出结果正常的原因,我猜测是因为你不是在运行“simpleClass.SecondField = "me";”这一语句之前进行以下操作(即鼠标移上去查看属性值)的。。。
你可以验证一下,不知道我的猜测对不对~
#33楼 [楼主] 2008-05-16 09:52 bartholomew
@球球
对VS来说是正常操作,但是我在调试的时候,不想让它被自动赋值啊啊啊……
#34楼 [楼主] 2008-05-16 09:54 bartholomew
@bangbang
用的是VS2008
你说的“debug可以去掉属性求值和隐式函数调用”,这个是在哪儿可以自行设置吗?
#35楼 [楼主] 2008-05-16 09:56 bartholomew
@BAsil
对,不要监视那个属性,而且,不能开启Autos窗口
PS:雨痕是谁?我怎么没看见他的留言呢~:)
#36楼 [楼主] 2008-05-16 09:58 bartholomew
@Q.yuhen
谢谢你的回复,很详细!
唉,这种错误会在不经意间碰到,有时会让人很郁闷……
#37楼 2008-05-16 09:58 Q.yuhen
Options -> Debugging -> General -> Enabled property evaluation and other implicit function calls
#38楼 [楼主] 2008-05-16 10:05 bartholomew
@Q.yuhen
OK!
这个方法很管用,设置后,似乎我提到的问题都解决了!非常感谢!
不过,更改这个设置后,会不会造成一些不便呢~?
#39楼 2008-05-16 10:06 PeterLeePersonal
楼主你好, 我是CTI VS IDE 组的TESTER, 根据你描述的情况, 我进行了试验.
最终确定你忽略了一个VS2008的设置, 请依次点击: Tools->Options->Debugging->General
找到一个叫Step Over Properties and Operators(Managed Only)选项, 并取消选中这个选项.
然后进行调试时就可以调试到Property的内部去了.
如果还有疑问, 你发邮件到PeterLeePersonal@gmail.com
#40楼 2008-05-16 10:13 Justin
这个问题不能怪VS,更不是什么VS的调试禁区,还是楼主自己搞晕了。
1、这段代码在逻辑上有问题,是导致这次灵异事件的一个主要原因;
public string OneField
{
get
{
if (_oneField.Length == 0)
{
_oneField = GetMyString();
}
return _oneField;
}
}
这段代码决定了OneField只能被执行一次,这么写到是没有问题,但是:
public string SecondField
{
get { return _secondField; }
set { _secondField = value; }
}
这个写法有问题,应该写成:
public string SecondField
{
get { return _secondField; }
set
{
_secondField = value;
_oneField = string.Empty;
}
}
也就是说,每次改变SecondField的时候,要清空OneField,如果这么做,就不会有问题了。
2、VS调试器实际上使用CLR调试API中的Eval方法执行对象中的属性和方法,这是VS调试器为方便开发调试而提供的一个非常实用的功能。也就是说,在你对OneField进行添加到监制、快速监视的时候,其实就已经执行过一遍Get方法了,OneField的长度已经大于0了,此后在任何时候也没有清空OneField,所以就不会再执行_oneField = GetMyString();了,这是楼主自己给自己下的套哦!
综上所述:可以解释所有灵异现象了吧,如果在 simpleClass.SecondField = "me";执行前查看了OneField的值(触发一次Eval方法执行OneField的Get),那么最后结果肯定就是“SecondField:”,此外结果都是“SecondField:me”。
#41楼 [楼主] 2008-05-16 10:28 bartholomew
@Justin
谢谢你这么详细的回复~
你说的两个问题:
第一,“这段代码在逻辑上有问题”,因为这段代码是别的同事写的,我在调试的时候也发现有些逻辑考虑得不是很严密,给我造成了很大的困扰~不过,我觉得造成我提出的问题的原因,在此处倒不是因为不严密的逻辑,还是VS太智能了,帮我们做了一些操作,而这些操作,却根本调试不进去,呵呵~
第二,我也不想给自己下套啊,这是VS的默认设置给我下的套啊,呵呵
#42楼 [楼主] 2008-05-16 10:29 bartholomew
@PeterLeePersonal
找不到你说的Step Over Properties and Operators(Managed Only)这个选项。。。
#43楼 2008-05-16 10:54 BAsil
楼主 21楼就是雨痕阿,呵呵
#44楼 2008-05-16 11:16 Duron800
学习了,雨痕在06年8月就发过类似问题的文章了。
#45楼 2008-05-16 11:52 随风流月
我很早就遇到过这样的问题 - 大凡能在 IDE 中自动取值的玩意儿都可能有同样的问题.