• 编程疑难杂症の无法剔除的神秘重复记录


    引言:在自己使用Microsoft Visual C# 速成版编程过程中,时不时总出现一些很郁闷的问题,一直尚未得到解决,在此特地列出来,向园里的朋友们求助讨论一番。

    注:本人已经Google和百度求助过,但是没有找到满意的答案,当然不排除自己的搜商还不够,假如哪位朋友能帮找出来,那更好!

    问题说明:

          出问题的还是自己的一个项目:照片信息管理器。上次才发生一起《编程疑难杂症の设置正确却无效的事件代码》,这次呢,很不幸的又出现了一个“神秘”的问题。image 如上图所示,程序读取了12条的测试样本信息,我的目的是测试“剔除重复信息”的功能。一看即知我想看到的结果是:当我点选了“剔除重复信息”的时候,ListView控件中只显示6条信息!很不幸的是,我点了,但是完全没有反应!

    image 为了验证我的测试,我把同一份信息样本读取两次,所以现在总共就有了24条,那么现在每条信息就有了3条重复的。我想要的结果应该是只剩下6条!糟糕的是,程序给我的结果是12条!也就第一幅截图,那里首次读取的12条。

    疑问:

          经过上面的测试,让我觉得,似乎这个样本中,原本应该是重复的信息,宛然就是独立的,不重复的!也就意味着本来12条中,应该有6条是重复的,但是在软件“看来”:它们就是不重复的12条!!

          假如说我的代码是完全错误的话,那么应该不会出现我后面测试的那种情况。因为,我用别的信息样本测试的时候,也都是完全正常的,而到了这里,假如把那样本中12条也“看做”是不重复的话,那么我的代码也算是工作正常的!

          至此,这个问题就显得有那么一点“神秘”了。

    调试跟踪:

          问题还在,终究还得解决的,所以还是不停的尝试着调试!首先贴出需要调试的代码:

            private void YERemoveTheSame()
            {
                //总是将当前列表框首次最完整的状态保存到lstListViewItems中。
                if ( lstListViewItems.Count == 0 )
                    lstListViewItems.AddRange(lsvMessageShower.Items.Cast<ListViewItem>());
    
                //将当前最新状态保存到lstListVIsame中。
                if ( ckbRemoveTheSame.Checked )
                {
                    lstListVIsame.Clear();
                    lstListVIsame.AddRange(lsvMessageShower.Items.Cast<ListViewItem>());
                    lsvMessageShower.Items.Clear();
                    //【剔除重复信息的代码】
                    lsvMessageShower.Items.AddRange(lstListVIsame.Distinct<ListViewItem>(new DistinctComparer()).ToArray());
                }
                else 
                {
                    lsvMessageShower.Items.Clear();
                    lsvMessageShower.Items.AddRange(lstListVIsame.ToArray());
                }
                //当lstListVIsame被初始化之时,不要更新界面,那样会造成选取动作无法执行。
                if ( lstListVIsame.Count != 0 )
                    YERefreshTheForm();
            }

    上面的代码是我的自定义方法,下面的是用于执行自定义剔除重复信息的比较器。lstListVIsame.Distinct<ListViewItem>(new DistinctComparer()).

    //用于剔除重复信息的比较器。
            class DistinctComparer : IEqualityComparer<ListViewItem>
            {
                #region IEqualityComparer<ListViewItem> 成员
    
    
                int i =0,j=0;
                public bool Equals(ListViewItem x , ListViewItem y)
                {
                    i++;
                    Debug.Print("【i】:" + i.ToString());
                    Debug.Print("【x】:" + x.SubItems[ 3 ].Text);
                    Debug.Print(x.SubItems[ 4 ].Text);
                    Debug.Print("【y】:" + y.SubItems[ 3 ].Text);
                    Debug.Print(y.SubItems[ 4 ].Text);
                    if ( x.SubItems[ 0 ].Text == y.SubItems[ 0 ].Text
                        && x.SubItems[ 1 ].Text == y.SubItems[ 1 ].Text
                        && x.SubItems[ 2 ].Text == y.SubItems[ 2 ].Text
                        && x.SubItems[ 3 ].Text == y.SubItems[ 3 ].Text
                        && x.SubItems[ 4 ].Text == y.SubItems[ 4 ].Text
                        )
                        return true;
                    return false;
                }
    
                public int GetHashCode(ListViewItem obj)
                {
    
                    j++;
                    if ( i != 0 )
                    {
                        Debug.Print("【j】:" + j.ToString());
                        Debug.Print("【obj】:" + obj.SubItems[ 3 ].Text);
                        Debug.Print(obj.SubItems[ 4 ].Text);
                    }
                    //必须返回ToString值的hashCode才能实现相同剔除。
                    //只要返回的时间,内容的HashCode相同,则百分之99以上是相同的了。
                    //这样Equals的调用量就会少很多,只是增加了此GetHashCode的计算量。
                    return obj.SubItems[ 3 ].Text.GetHashCode() + obj.SubItems[ 4 ].Text.GetHashCode();
                }
    
                #endregion
            }

          在调试跟踪的时候,发现代码首先执行比较器中的GetHashCode函数,当返回的HashCode有相同记录的时候,才开始执行Equals函数。所以在跟踪调试第一截图的测试情况的时候,设置在Equals函数处的断点根本没有捕捉到!后来分析这种情况的话,可能的问题就出在GetHashCode函数里。我把断点放到了该函数里,然后对其进行逐语句调试。结果执行正常!这里的正常意思是说,执行到最后,都没有一次跳入Equals函数执行(这句貌似有点废话)。

          但是,按照自己的代码思路,GetHashCode函数最后返回的是第4列的文本HashCode加上第5列的文本HashCode的和。之所以这样做,注释里面已经给出原因。因此自己“下意识”的觉得这里不会出现问题的,因为看截图一就知道,12条样本中的凡是相同信息的第4,5列都是相同的!既然相同,返回的HashCode也应该是相同的,那么HashCode都相同了,为什么它就死活不执行Equals函数呢!分析到此,彻底陷入僵局。

    ——2010年11月23日 20:38:08

    功夫不负有心人:

          这个问题出现后,死活想不通,心里就有了一个“坎”。要是解决不了的话,我想自己一定放不下的!所以呢,面对着“貌似正确”的代码,无论如何也要找出错误来!为了方便调试,我把比较器的代码修改了一下。如下:

                public int GetHashCode(ListViewItem obj)
                {
    
                    j++;
                    if ( i != 0 )
                    {
                        Debug.Print("【j】:" + j.ToString());
                        Debug.Print("【obj】:" + obj.SubItems[ 3 ].Text);
                        Debug.Print(obj.SubItems[ 4 ].Text);
                    }
                    //必须返回ToString值的hashCode才能实现相同剔除。
                    //只要返回的时间,内容的HashCode相同,则百分之99以上是相同的了。
                    //这样Equals的调用量就会少很多,只是增加了此GetHashCode的计算量。
                    int a = obj.SubItems[ 3 ].Text.GetHashCode();
                    int b = obj.SubItems[ 4 ].Text.GetHashCode();
                    Debug.Print("4l:" + obj.SubItems[ 3 ].Text + ",HashCode:" + a.ToString());
                    Debug.Print("5l:" + obj.SubItems[ 4 ].Text + ",HashCode:" + b.ToString());
                    Debug.Print("+" + ( a + b ).ToString());
                    return a+b;
                }

    这里呢目的是为了分离两列的HashCode,以充分分析两列“看似相同”的信息是否真的相同。结果呢,即时窗口真的出现了猫腻!

    第4列:2010-09-06 00:03:14,HashCode:468120897
    第5列:呵呵,那就是畸形的咯!
    ,HashCode:1924439105
    相加=-1902407294
    第4列:2010-09-06 00:03:14,HashCode:468120897
    第5列:呵呵,那就是畸形的咯!,HashCode:350460993
    相加=818581890
    第4列:2010-09-06 00:01:36,HashCode:-962327541
    第5列:不是梦游,是遨遥!
    ,HashCode:30809777
    相加=-931517764
    第4列:2010-09-06 00:01:36,HashCode:-962327541
    第5列:不是梦游,是遨遥!,HashCode:31137457
    相加=-931190084

    从这两条摘录的信息就可以看到,我们“看似”重复的第二条信息,输出内容居然不同第一条!因为第一条的“HashCode”是紧接着信息后面,而第二条的,却是在下一行里面!这就是问题的源头了吧!为了明确找出到底是有什么不同,我使用“局部变量”窗口查看当时的变量值:wps_clip_image-14091o(∩_∩)o 哈哈,终于露出马脚了!信息的后面不知道为什么居然多出了一个【\r】符号!

     

    知道问题原因之后,接下来只要在读取函数那里做稍稍的修改就OK了!至此完美解决此问题!

    终于解决了!心里舒坦啊!无比爽啊……嘿嘿

    事后感:

    不要盲目相信我们的眼睛,“眼见”再一次被证明了“不一定为实”!因此,在以后还是要多多测试,多多看看中间的变量值。不过话说似乎只有在调试的窗口中,才能看到一般没有显示“控制符”。这也是导致调试的时候没有注意到后面不小心多出了一个东西的原因吧。

    [By:Asion Tang] 博客园 标签: C#代码调试
    作者:Asion Tang
    凡是没有注明[转载]的文章,本Blog发表的文章版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    0045 Spring中使用DataSourceTransactionManager进行事务管理的xml配置
    0044 spring框架的applicationContext.xml的命名空间
    0042 MySQL学习笔记-入门--01
    0041 Java学习笔记-多线程-线程池、ForkJoinPool、ThreadLocal
    0040 Java学习笔记-多线程-线程run()方法中的异常
    0039 Java学习笔记-多线程-线程控制、线程组
    ThinkPHP5.0 实现 app微信支付功能
    Thinkphp5.0分页和跳页
    php header函数导出excel表格
    php之微信公众号发送模板消息
  • 原文地址:https://www.cnblogs.com/AsionTang/p/1885872.html
Copyright © 2020-2023  润新知