编程疑难杂症の莫名的神秘联结
引言:在自己使用Microsoft Visual C# 速成版编程过程中,时不时总出现一些很郁闷的问题,一直尚未得到解决,在此特地列出来,向园里的朋友们求助讨论一番。
注:本人已经Google和百度求助过,但是没有找到满意的答案,当然不排除自己的搜商还不够,假如哪位朋友能帮找出来,那更好!
问题说明:
最近项目“聊天记录管理器”已经趋于稳定了。绝大部分的功能已经定型并且可用了。
所以接下来的工作基本上就是对代码进行维护,并逐渐完善原有功能。就在这个过程中,就又出现了一个莫名不得其解的问题。详细描述如下:
这次要进行完善的功能是:【自动检测飞信号码】功能。为此已经完整的重构到如下函数中:
/// <summary> /// 自动检测短信中是否有飞信聊天记录。 /// </summary> private void YEAutoCheckFeiXin() { if ( ckbAutoCheckFeixin.Checked ) { lstFeiXinBackup.Clear(); //将当前状态备份为一个快照。即不想检测飞信的时候能够恢复原来的数据。 lstFeiXinBackup.AddRange(lsvMessageShower.Items.Cast<ListViewItem>()); Debug.Print(lstFeiXinBackup[ 1 ].SubItems[ 1 ].Text);//显示已经被赋值后的lstFeiXinBackup变量里面的第二个元素的值。 foreach ( ListViewItem tmp in lsvMessageShower.Items ) { if ( tmp.SubItems[ 0 ].Text != "sms" ) continue; if ( tmp.SubItems[ 1 ].Text.Contains("12520") ) { tmp.SubItems[ 1 ].Text = tmp.SubItems[ 4 ].Text.Remove(tmp.SubItems[ 4 ].Text.IndexOf(':')); tmp.SubItems[ 4 ].Text = tmp.SubItems[ 4 ].Text.Remove(0 , tmp.SubItems[ 4 ].Text.IndexOf(':') + 1); } else if ( tmp.SubItems[ 2 ].Text.Contains("12520") ) tmp.SubItems[ 2 ].Text = MyNickName.Text;//作为个人网名 else continue; //设置为飞信的时间格式。 tmp.SubItems[ 3 ].Text = tmp.SubItems[ 3 ].Text.Replace('.' , '-'); tmp.SubItems[ 3 ].Text += ":00"; } Debug.Print(lstFeiXinBackup[ 1 ].SubItems[ 1 ].Text);//再次显示该元素的值,用于和第一个显示的值想比较。 } else { if ( lstFeiXinBackup.Count == 0 ) return; lsvMessageShower.Items.Clear(); lsvMessageShower.Items.AddRange(lstFeiXinBackup.ToArray()); //马上初始化此快照。 lstFeiXinBackup.Clear(); } }
这个函数在“自动检测飞信”的复选框按钮的Checked属性更改的时候调用。当勾选此复选框之后,进入检测代码;首先保存一个当前列表框的状态,这里我使用的是泛型List<>来对其进行保存,由于ListView控件本身没有对应函数转换成List可以AddRange的格式,所以这里我使用Cast<Type>来满足这个需求。
代码很成功的把ListView控件中的所有数据保存到了泛型List变量里面了,第一个Debug正确打印出了一个值,是未经修改的手机号码如下格式:12520xxxxxxxxxx;这是飞信的号码段。于是在下面的Foreach循环中不断的检测每一行,把凡是此号码段的都修改为标准的飞信聊天记录格式:(XX网名,时间,聊天内容)。而这个操作呢就是对ListView控件中的发送人1手机号码进行修改为对应网名。
接下来,问题就来咯。在第二个Debug的打印中,发现之前打印的那个元素此刻的值居然也发生了改变。打印出的是在Foreach循环中改变后的值!
症状诊断:
打一出现这个问题,我就想到了一个词“引用类型”。难道问题的源头就是引用类型?!感觉翻看曾经的读书笔记:
果然不出所料。借此机会强化了对泛型List<Type>的印象了。在此引用微软帮助如下:
性能注意事项:
在决定使用 List<(Of <(T>)>) 还是使用 ArrayList 类(两者具有类似的功能)时,记住 List<(Of <(T>)>) 类在大多数情况下执行得更好并且是类型安全的。如果对 List<(Of <(T>)>) 类的类型 T 使用引用类型,则两个类的行为是完全相同的。但是,如果对类型 T 使用值类型,则需要考虑实现和装箱问题。
由此可以推知我使用的ListViewItem类型是引用类型。赶紧查看一下帮助。
暂时没有找到解决方案!
[By:Asion Tang]2010年12月17日 20:43:07
解决方案:
今天一早起床(呵呵,其实是中午十二点才起的),当自己再次面对昨天的这个问题,思考怎么对付的时候,有了一个不错的想法:既然是在引用类型出了错,那么要解决的问题就是该怎么样实现深度复制(Deep Copy)的功能,负责把ListView控件里面的每一项数据完整而独立的生成一个备份!
记得昨天在查询ListViewItem类型的MSDN帮助的时候,发现了在它的类声明里,实现了ICloneable借口!意思就是说
列表框的[数据项]本身其实提供了一个Clone方法,貌似可以实现深度复制(Deep Copy)功能。于是赶紧编码测试,修改后代码如下:
/// <summary> /// 自动检测短信中是否有飞信聊天记录。 /// </summary> private void YEAutoCheckFeiXin() { if ( ckbAutoCheckFeixin.Checked ) { lstFeiXinBackup.Clear(); foreach ( ListViewItem tmp in lsvMessageShower.Items ) { /* 将当前状态备份为一个快照。即不想检测飞信的时候能够恢复原来的数据。 * 必须使用这里的Clone函数对每一项进行克隆,才能达到深度复制。否则ListViewItem作为引用类型, * 使用直接赋值的话,控件里的项更改后,存在lstFeiXinBackup里面的项的值也会更改!*/ lstFeiXinBackup.Add((ListViewItem)tmp.Clone()); if ( tmp.SubItems[ 0 ].Text != "sms" ) continue; if ( tmp.SubItems[ 1 ].Text.Contains("12520") ) { tmp.SubItems[ 1 ].Text = tmp.SubItems[ 4 ].Text.Remove(tmp.SubItems[ 4 ].Text.IndexOf(':')); tmp.SubItems[ 4 ].Text = tmp.SubItems[ 4 ].Text.Remove(0 , tmp.SubItems[ 4 ].Text.IndexOf(':') + 1); } else if ( tmp.SubItems[ 2 ].Text.Contains("12520") ) tmp.SubItems[ 2 ].Text = MyNickName.Text;//作为个人网名 else continue; //设置为飞信的时间格式。 tmp.SubItems[ 3 ].Text = tmp.SubItems[ 3 ].Text.Replace('.' , '-'); tmp.SubItems[ 3 ].Text += ":00"; } } else { if ( lstFeiXinBackup.Count == 0 ) return; lsvMessageShower.Items.Clear(); lsvMessageShower.Items.AddRange(lstFeiXinBackup.ToArray()); //马上初始化此快照。 lstFeiXinBackup.Clear(); } }
其实我仅仅修改了一处的代码,那就是把原先直接由ListView控件的Items属性转换而来的数据,改为了在Foreach循环中,一条一条的Clone添加到变量中。
经过测试,完美解决问题!
[By:Asion Tang]
2010年12月18日 22:01:40